├── .editorconfig
├── .gitignore
├── .npmignore
├── .travis.yml
├── Gruntfile.js
├── LICENSE
├── README.md
├── example
├── .gitignore
├── DefaultIcon.png
├── Resources
│ └── app.js
├── app
│ ├── config.json
│ ├── controllers
│ │ └── index.js
│ ├── lib
│ │ └── xp.ui.js
│ ├── styles
│ │ └── app.tss
│ ├── views
│ │ └── index.xml
│ └── widgets
│ │ └── nl.fokkezb.html2as.widget
│ │ ├── README.md
│ │ ├── controllers
│ │ └── widget.js
│ │ ├── views
│ │ └── widget.xml
│ │ └── widget.json
├── plugins
│ └── ti.alloy
│ │ ├── hooks
│ │ ├── alloy.js
│ │ └── deepclean.js
│ │ └── plugin.py
└── tiapp.xml
├── index.js
├── package.json
├── screencast.gif
├── spec
└── support
│ └── jasmine.json
└── test
├── specs
└── indexSpec.js
└── spies
└── TiSpy.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /coverage
2 | /node_modules
3 | /spec
4 | /test
5 | .DS_Store
6 | /*.zip
7 | /npm-debug.log
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /.git
2 | /coverage
3 | /example
4 | /node_modules
5 | /spec
6 | /test
7 | .DS_Store
8 | /.editorconfig
9 | /.gitignore
10 | /.travis.yml
11 | /*.zip
12 | /npm-debug.log
13 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: cpp
2 | env:
3 | - NODE_VERSION="0.10"
4 | - NODE_VERSION="0.12"
5 | # Disabling versions that fail because of https://github.com/smclab/grunt-titaniumifier/issues/3
6 | # - NODE_VERSION="4.4"
7 | # - NODE_VERSION="5.9"
8 | os:
9 | - osx
10 | matrix:
11 | fast_finish: true
12 | before_install:
13 | - git clone https://github.com/creationix/nvm.git /tmp/.nvm;
14 | - source /tmp/.nvm/nvm.sh;
15 | - nvm install $NODE_VERSION;
16 | - nvm use --delete-prefix $NODE_VERSION;
17 | install:
18 | - npm install
19 | script:
20 | - npm test
21 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function(grunt) {
4 |
5 | grunt.initConfig({
6 |
7 | pkg: grunt.file.readJSON('package.json'),
8 |
9 | clean: {
10 | unzip: ['modules'],
11 | modules: ['example/modules'],
12 | app: ['example/build']
13 | },
14 |
15 | titaniumifier: {
16 | module: {
17 | src: '.',
18 | dest: '.'
19 | }
20 | },
21 |
22 | titanium: {
23 | ios: {
24 | options: {
25 | command: 'build',
26 | logLevel: 'debug',
27 | projectDir: './example',
28 | platform: 'ios'
29 | }
30 | }
31 | },
32 |
33 | unzip: {
34 | module: {
35 | src: 'nl.fokkezb.html2as-commonjs-<%= pkg.version %>.zip',
36 | dest: 'example'
37 | }
38 | }
39 |
40 | });
41 |
42 | grunt.loadNpmTasks('grunt-contrib-clean');
43 | grunt.loadNpmTasks('grunt-titaniumifier');
44 | grunt.loadNpmTasks('grunt-titanium');
45 | grunt.loadNpmTasks('grunt-zip');
46 |
47 | grunt.registerTask('build', ['titaniumifier:module']);
48 | grunt.registerTask('update', ['unzip:module']);
49 | grunt.registerTask('test', ['update', 'titanium:ios', 'clean:unzip']);
50 |
51 | grunt.registerTask('ios', ['clean', 'build', 'test']);
52 |
53 | grunt.registerTask('default', ['ios']);
54 | };
55 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | // The MIT License (MIT)
2 |
3 | // Copyright (c) 2015 Fokke Zandbergen
4 |
5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | // this software and associated documentation files (the "Software"), to deal in
7 | // the Software without restriction, including without limitation the rights to
8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | // the Software, and to permit persons to whom the Software is furnished to do so,
10 | // 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, FITNESS
17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # HTML to Attributed String
2 |
3 | [](https://david-dm.org/fokkezb/ti-html2as#info=dependencies)
4 | [](https://david-dm.org/fokkezb/ti-html2as#info=devDependencies) [](https://travis-ci.org/FokkeZB/ti-html2as)
5 |
6 | HTML to [Ti.UI.AttributedString](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.AttributedString) parser for [Titanium](http://appcelerator.com/titanium).
7 |
8 | ## Screencast
9 |
10 | 
11 |
12 | ## Usage
13 | A packaged *CommonJS* module can be found via [Releases](https://github.com/fokkezb/ti-html2as/releases). Follow the guide on [Using a Module](http://docs.appcelerator.com/titanium/latest/#!/guide/Using_a_Module) or use gitTio:
14 |
15 | gittio install nl.fokkezb.html2as
16 |
17 | The module exports a single function that takes an HTML string and a callback to receive an error or [Ti.UI.AttributedString](http://docs.appcelerator.com/platform/latest/#!/api/Titanium.UI.AttributedString) object.
18 |
19 | ```
20 | var html2as = require('nl.fokkezb.html2as');
21 |
22 | html2as(
23 | 'Hello Bold World',
24 | function(err, as) {
25 |
26 | if (err) {
27 | console.error(err);
28 |
29 | } else {
30 |
31 | var label = Titanium.UI.createLabel({
32 | attributedString: as
33 | });
34 |
35 | label.addEventListener('link', function(e) {
36 | alert('Longtap on link to: ' + e.url);
37 | });
38 |
39 | view.add(label);
40 | }
41 | }
42 | );
43 | ```
44 |
45 | ### Alloy
46 |
47 | In Alloy you can use the [XP.UI](https://github.com/FokkeZB/UTiL/blob/master/docs/xp.ui.md#tag-label) CommonJS module to emulate the `html` attribute Android has for ``:
48 |
49 | ```
50 |
51 |
52 |
53 |
54 |
55 | ```
56 |
57 | Or you can use the [Widget](https://github.com/FokkeZB/nl.fokkezb.html2as.widget), which has the advantage that you can set the `html` property in a later stage as well:
58 |
59 | ```
60 |
61 |
62 |
63 |
64 |
65 | ```
66 |
67 | ## Supported tags and attributes
68 |
69 | Standard (old) HTML:
70 |
71 | * ``, ``
72 | * ``
73 | * ``, ``
74 | * ``, ``, ``
75 | * ``
76 | * ``
77 |
78 | Non-standard:
79 |
80 | * `` (letterpress-style)
81 | * `` (spacing between characters)
82 | * `` (stretch text horizontally)
83 |
84 | ## Custom matcher
85 | The optional custom matcher allows parsing of non-standard tags and attributes.
86 |
87 | The function needs to return an object containing two properties:
88 |
89 | * `parameters` {Object} the parameters property passed into the function with any additions
90 | * `continue` {Boolean} whether to continue through the default matchers
91 |
92 | ### Custom matcher example
93 | To style H1 tags:
94 |
95 | ```
96 | var customMatcher = function(node, parameters, outerFont, offset, length, ns) {
97 | if (node.type === 'tag' && node.name && node.name === 'h1') {
98 | parameters.attributes.unshift({
99 | type: ns.ATTRIBUTE_FOREGROUND_COLOR,
100 | value: Alloy.CFG.h1Color,
101 | range: [offset, length]
102 | });
103 | parameters.attributes.unshift({
104 | type: ns.ATTRIBUTE_FONT,
105 | value: {
106 | fontSize: Alloy.CFG.h1FontSize,
107 | fontFamily: Alloy.CFG.h1FontFamily
108 | },
109 | range: [offset, length]
110 | });
111 | }
112 |
113 | return {
114 | parameters: parameters,
115 | continue: true
116 | };
117 | }
118 |
119 | var html2as = require('nl.fokkezb.html2as');
120 |
121 | html2as(
122 | 'Hello World
',
123 | function(err, as) {
124 |
125 | if (err) {
126 | console.error(err);
127 | } else {
128 | view.add(Titanium.UI.createLabel({
129 | attributedString: as
130 | }));
131 | }
132 | },
133 | customMatcher
134 | );
135 | ```
136 |
137 | ## Example
138 | The [example](example) folder includes a Titanium project demonstrating the module. To build the module and then run the example project use the included grunt tasks:
139 |
140 | ```
141 | npm install
142 | grunt
143 | ```
144 |
145 | ## Changelog
146 |
147 | * 1.3.1: Fixes #21
148 | * 1.3.0: Adds support for `Ti.UI.AttributedString`, backwards compatible with `Ti.UI.iOS.AttributedString`.
149 | * 1.2.0: Adds [support for HTML entities](https://github.com/FokkeZB/ti-html2as/pull/5)
150 | * 1.1.0: Updated for Titaniumifier 1.0.0
151 | * 1.0.0: Initial version
152 |
153 | ## Issues
154 |
155 | Please report issues and features requests in the repo's [issue tracker](https://github.com/fokkezb/ti-html2as/issues).
156 |
157 |
158 | ## Credits
159 |
160 | * [@patrickknaap](https://github.com/patrickknaap) for adding support of HTML entities
161 | * [@fb55](https://github.com/fb55) for [htmlparser2](https://github.com/fb55/htmlparser2)
162 | * [@smclab](https://github.com/smclab/titaniumifier) for [titaniumifier](https://github.com/smclab/titaniumifier)
163 |
164 |
165 | ## License
166 |
167 | See [LICENSE](LICENSE).
168 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /Resources
3 | build.log
4 | /build
5 | npm-debug.log
6 | tmp
7 | .map
8 | .project
9 | .settings
10 | Thumbs.db
11 | /i18n
12 | /platform
13 | /modules
14 |
--------------------------------------------------------------------------------
/example/DefaultIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonkneen/ti-html2as/c5bd034370840da52211beaedcfbe408ea4dcbba/example/DefaultIcon.png
--------------------------------------------------------------------------------
/example/Resources/app.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonkneen/ti-html2as/c5bd034370840da52211beaedcfbe408ea4dcbba/example/Resources/app.js
--------------------------------------------------------------------------------
/example/app/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "global": {},
3 | "env:development": {},
4 | "env:test": {},
5 | "env:production": {},
6 | "os:android": {},
7 | "os:blackberry": {},
8 | "os:ios": {},
9 | "os:mobileweb": {},
10 | "dependencies": {
11 | "nl.fokkezb.html2as.widget": "*"
12 | }
13 | }
--------------------------------------------------------------------------------
/example/app/controllers/index.js:
--------------------------------------------------------------------------------
1 | var html2as = require('nl.fokkezb.html2as');
2 |
3 | $.index.open();
4 |
5 | html2as(
6 | '\n' +
7 | 'strong or b
\n' +
8 | 'u\n' +
9 | 'strike or del or s\n' +
10 | 'font:face\n' +
11 | 'font:size\n' +
12 | 'font:color\n' +
13 | 'font:face+size+color\n' +
14 | 'a:href (longtap with older SDKs)\n' +
15 | 'and character entities: & © \n' +
16 | 'Hallò world \n' +
17 | '\nNon-standard attributes:\n\n' +
18 | 'effect\n' +
19 | 'kern:value\n' +
20 | 'expansion:value\n' +
21 | 'br
br
br\n' +
22 | '',
23 | function(err, as) {
24 |
25 | if (err) {
26 | console.error(err);
27 |
28 | } else {
29 | $.basicLabel.attributedString = as;
30 | }
31 | });
32 |
33 | html2as(
34 | 'Hello Red World',
35 | function(err, as) {
36 |
37 | if (err) {
38 | console.error(err);
39 |
40 | } else {
41 | $.listViewSection.setItems([{
42 | label: {
43 | attributedString: as
44 | }
45 | }]);
46 | }
47 | });
48 |
49 | function onLink(e) {
50 | alert('Handle link to: ' + e.url);
51 | }
52 |
--------------------------------------------------------------------------------
/example/app/lib/xp.ui.js:
--------------------------------------------------------------------------------
1 | if (!OS_IOS) {
2 |
3 | var NavigationWindow = function(args) {
4 | this.args = args;
5 | };
6 |
7 | NavigationWindow.prototype.open = function(params) {
8 | params = params || {};
9 | params.displayHomeAsUp = false;
10 | return this.openWindow(this.args.window, params);
11 | };
12 |
13 | NavigationWindow.prototype.close = function(params) {
14 | return this.closeWindow(this.args.window, params);
15 | };
16 |
17 | NavigationWindow.prototype.openWindow = function(window, options) {
18 | var that = this;
19 |
20 | options = options || {};
21 | options.swipeBack = (typeof options.swipeBack === 'boolean') ? options.swipeBack : that.args.swipeBack;
22 | options.displayHomeAsUp = (typeof options.displayHomeAsUp === 'boolean') ? options.displayHomeAsUp : that.args.displayHomeAsUp;
23 |
24 | if (OS_ANDROID && options.animated !== false) {
25 | options.activityEnterAnimation = Ti.Android.R.anim.slide_in_left;
26 | options.activityExitAnimation = Ti.Android.R.anim.slide_out_right;
27 | }
28 |
29 | if (options.swipeBack !== false) {
30 | window.addEventListener('swipe', function(e) {
31 | if (e.direction === 'right') {
32 | that.closeWindow(window, options);
33 | }
34 | });
35 | }
36 |
37 | if (OS_ANDROID && options.displayHomeAsUp !== false && !window.navBarHidden) {
38 | window.addEventListener('open', function() {
39 | var activity = window.getActivity();
40 | if (activity) {
41 | var actionBar = activity.actionBar;
42 | if (actionBar) {
43 | actionBar.displayHomeAsUp = true;
44 | actionBar.onHomeIconItemSelected = function() {
45 | that.closeWindow(window, options);
46 | };
47 | }
48 | }
49 | });
50 | }
51 |
52 | return window.open(options);
53 | };
54 |
55 | NavigationWindow.prototype.closeWindow = function(window, options) {
56 | options = options || {};
57 |
58 | if (OS_ANDROID && options.animated !== false) {
59 | options.activityEnterAnimation = Ti.Android.R.anim.slide_in_left;
60 | options.activityExitAnimation = Ti.Android.R.anim.slide_out_right;
61 | }
62 |
63 | return window.close(options);
64 | };
65 | }
66 |
67 | exports.createNavigationWindow = function(args) {
68 | var navWin = OS_IOS ? Ti.UI.iOS.createNavigationWindow(args) : new NavigationWindow(args);
69 |
70 | if (args && args.id) {
71 | Alloy.Globals[args.id] = navWin;
72 | }
73 |
74 | return navWin;
75 | };
76 |
77 | exports.createWindow = function(args) {
78 |
79 | if (OS_IOS) {
80 | return Ti.UI.createWindow(args);
81 | } else {
82 | return Ti.UI.createView(args);
83 | }
84 | };
85 |
86 | exports.createTextArea = function(args) {
87 | var $textArea = Ti.UI.createTextArea(args);
88 |
89 | if (args.hintText) {
90 | $textArea.originalColor = $textArea.color || '#000';
91 | if (!$textArea.value) {
92 | $textArea.applyProperties({
93 | value: $textArea.hintText,
94 | color: '#ccc'
95 | });
96 | }
97 |
98 | $textArea.addEventListener('focus', function(e){
99 | if (e.source.value==e.source.hintText) {
100 | e.source.applyProperties({
101 | value: '',
102 | color: e.source.originalColor
103 | });
104 | }
105 | });
106 |
107 | $textArea.addEventListener('blur', function(e){
108 | if (!e.source.value) {
109 | e.source.applyProperties({
110 | value: e.source.hintText,
111 | color: '#ccc'
112 | });
113 | }
114 | });
115 | }
116 |
117 | return $textArea;
118 | };
119 |
120 | exports.createLabel = function createLabel(args) {
121 |
122 | if (OS_IOS && args.html) {
123 | var html = args.html;
124 |
125 | delete args.text;
126 | delete args.html;
127 |
128 | var label = Ti.UI.createLabel(args);
129 | var ref = label;
130 |
131 | var html2as = require('nl.fokkezb.html2as');
132 |
133 | html2as(html, function(err, attr) {
134 |
135 | if (err) {
136 | console.error(err);
137 |
138 | } else {
139 | ref.attributedString = attr;
140 | }
141 |
142 | ref = null;
143 | });
144 |
145 | return label;
146 |
147 | } else {
148 | return Ti.UI.createLabel(args);
149 | }
150 | };
151 |
--------------------------------------------------------------------------------
/example/app/styles/app.tss:
--------------------------------------------------------------------------------
1 | 'Window[platform=ios]': {
2 | backgroundColor: 'white'
3 | }
4 |
--------------------------------------------------------------------------------
/example/app/views/index.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/example/app/widgets/nl.fokkezb.html2as.widget/README.md:
--------------------------------------------------------------------------------
1 | # HTML to Attributed String Widget
2 |
3 | This widget wraps the [HTML to Attributed String Module](http://gitt.io/component/nl.fokkezb.html2as) so you can easily use it in Alloy instead of a `` for cross-browser support for the originally Android-only `html` attribute.
4 |
5 | ## Get it [](http://gitt.io/component/nl.fokkezb.html2as.widget)
6 |
7 | * Install via [gitTio](http://gitt.io/component/nl.fokkezb.html2as.widget):
8 |
9 | $ gittio install nl.fokkezb.html2as.widget
10 |
11 | giTio will automatically install the [HTML2AS module](http://gitt.io/component/nl.fokkezb.html2as) the widget depends on.
12 |
13 | * Or download a [release](https://github.com/FokkeZB/nl.fokkezb.html2as.widget/releases), extract it to your app's `app/widgets/nl.fokkezb.html2as.widget` folder and add the dependency to your `config.json`:
14 |
15 | {
16 | ..
17 | "dependencies": {
18 | "nl.fokkezb.html2as.widget": "*"
19 | ..
20 | }
21 | }
22 |
23 | ## Use it
24 |
25 | **index.xml**
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 | Of course you can also set any property for the widget via `index.tss`.
37 |
38 | **index.js**
39 |
40 | function onLinkHandler(e) {
41 | alert(e.url);
42 |
43 | $.h2a.html = 'Bye Google!';
44 | }
45 |
46 | ## API
47 |
48 | ### on/addEventListener()
49 |
50 | Add an event-listener to the label.
51 |
52 | ### off/removeEventListener()
53 |
54 | Remove an event-listener from the label.
55 |
56 | ### trigger/fireEvent()
57 |
58 | Fire an event on the label.
59 |
60 | ### setHtml()
61 |
62 | Set the HTML (on iOS parsed to Attributed String).
63 |
64 | ### getHtml()
65 |
66 | Get the HTML.
67 |
68 | ### applyProperties()
69 |
70 | Apply properties to the label
71 |
72 | ### html
73 |
74 | Set or get the HTML property (on iOS parsed to Attributed String).
75 |
76 | ## Known issues
77 |
78 | See the *Known issues* paragraph of the [Using basic HTML in Labels on iOS like Android](http://www.tidev.io/2014/11/13/using-basic-html-in-labels-on-ios-like-android/) article on tiDev.
79 |
80 | ## License
81 |
82 | The MIT License (MIT)
83 |
84 | Copyright (c) 2015 Fokke Zandbergen
85 |
86 | Permission is hereby granted, free of charge, to any person obtaining a copy
87 | of this software and associated documentation files (the "Software"), to deal
88 | in the Software without restriction, including without limitation the rights
89 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
90 | copies of the Software, and to permit persons to whom the Software is
91 | furnished to do so, subject to the following conditions:
92 |
93 | The above copyright notice and this permission notice shall be included in all
94 | copies or substantial portions of the Software.
95 |
96 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
97 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
98 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
99 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
100 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
101 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
102 | SOFTWARE.
103 |
--------------------------------------------------------------------------------
/example/app/widgets/nl.fokkezb.html2as.widget/controllers/widget.js:
--------------------------------------------------------------------------------
1 | var html2as = require('nl.fokkezb.html2as');
2 |
3 | $.applyProperties = function applyProperties(props) {
4 |
5 | if (props.html) {
6 | $.html = props.html;
7 | }
8 |
9 | $.label.applyProperties(props);
10 | };
11 |
12 | $.on = $.addEventListener = function(name, callback) {
13 | return $.label.addEventListener(name, callback);
14 | };
15 |
16 | $.off = $.removeEventListener = function(name, callback) {
17 | return $.label.removeEventListener(name, callback);
18 | };
19 |
20 | $.trigger = $.fireEvent = function(name, e) {
21 | return $.label.fireEvent(name, e);
22 | };
23 |
24 | $.setHtml = function setHtml(html) {
25 |
26 | $.label.html = html;
27 |
28 | if (OS_IOS) {
29 | html2as(html, function handle(err, as) {
30 |
31 | if (err) {
32 | console.error('[nl.fokkezb.html2as.widget] ' + err);
33 |
34 | } else {
35 | $.label.attributedString = as;
36 | }
37 | });
38 | }
39 |
40 | };
41 |
42 | $.getHtml = function getHtml() {
43 | return $.label.html;
44 | };
45 |
46 | Object.defineProperty($, 'html', {
47 | get: $.getHtml,
48 | set: $.setHtml
49 | });
50 |
51 | $.applyProperties(arguments[0] || {});
--------------------------------------------------------------------------------
/example/app/widgets/nl.fokkezb.html2as.widget/views/widget.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/example/app/widgets/nl.fokkezb.html2as.widget/widget.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "nl.fokkezb.html2as.widget",
3 | "name": "HTML 2 Attributed String Widget",
4 | "description" : "Widget wrapping the HTML 2 Attributed String module, providing cross-platform HTML attribute for labels.",
5 | "author": "Fokke Zandbergen",
6 | "version": "1.0",
7 | "copyright":"Copyright (c) 2015 Fokke Zandbergen",
8 | "license":"MIT",
9 | "min-alloy-version": "1.5",
10 | "min-titanium-version":"3.4",
11 | "tags":"attributed strings, html",
12 | "platforms":"ios",
13 | "modules": {
14 | "nl.fokkezb.html2as": "*"
15 | }
16 | }
--------------------------------------------------------------------------------
/example/plugins/ti.alloy/hooks/alloy.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Alloy
3 | * Copyright (c) 2012 by Appcelerator, Inc. All Rights Reserved.
4 | * See LICENSE for more information on licensing.
5 | */
6 |
7 | exports.cliVersion = '>=3.X';
8 | exports.version = '1.0.0';
9 | var SILENT = true;
10 |
11 | exports.init = function (logger, config, cli, appc) {
12 | var path = require('path'),
13 | fs = require('fs'),
14 | afs = appc.fs,
15 | i18n = appc.i18n(__dirname),
16 | __ = i18n.__,
17 | __n = i18n.__n,
18 | pkginfo = appc.pkginfo.package(module),
19 | exec = require('child_process').exec,
20 | spawn = require('child_process').spawn,
21 | parallel = appc.async.parallel;
22 |
23 | if(!process.env.sdk) {
24 | process.env.sdk = cli.sdk.name;
25 | }
26 |
27 | function run(deviceFamily, deployType, target, finished, silent) {
28 | var appDir = path.join(cli.argv['project-dir'], 'app');
29 | if (!afs.exists(appDir)) {
30 | logger.info(__('Project not an Alloy app, continuing'));
31 | finished();
32 | return;
33 | }
34 | logger.info(__('Found Alloy app in %s', appDir.cyan));
35 |
36 | // TODO: Make this check specific to a TiSDK version
37 | // create a .alloynewcli file to tell old plugins not to run
38 | var buildDir = path.join(cli.argv['project-dir'], 'build');
39 | if (!afs.exists(buildDir)) {
40 | fs.mkdirSync(buildDir);
41 | }
42 | fs.writeFileSync(path.join(buildDir, '.alloynewcli'), '');
43 |
44 | var cRequire = afs.resolvePath(__dirname, '..', 'Alloy', 'commands', 'compile', 'index.js'),
45 | config = {
46 | platform: /(?:iphone|ipad)/.test(cli.argv.platform) ? 'ios' : cli.argv.platform,
47 | version: '0',
48 | simtype: 'none',
49 | devicefamily: /(?:iphone|ios)/.test(cli.argv.platform) ? deviceFamily : 'none',
50 | deploytype: deployType || cli.argv['deploy-type'] || 'development',
51 | target: target
52 | };
53 | if(silent) {
54 | // turn off all logging output for code analyzer build hook
55 | config.noBanner = 'true';
56 | config.logLevel = '-1';
57 | }
58 |
59 | config = Object.keys(config).map(function (c) {
60 | return c + '=' + config[c];
61 | }).join(',');
62 |
63 | if (afs.exists(cRequire)) {
64 | // we're being invoked from the actual alloy directory!
65 | // no need to subprocess, just require() and run
66 | var origLimit = Error.stackTraceLimit;
67 | Error.stackTraceLimit = Infinity;
68 | try {
69 | require(cRequire)({}, {
70 | config: config,
71 | outputPath: cli.argv['project-dir'],
72 | _version: pkginfo.version
73 | });
74 | } catch (e) {
75 | logger.error(__('Alloy compiler failed'));
76 | e.toString().split('\n').forEach(function (line) {
77 | if (line) { logger.error(line); }
78 | });
79 | process.exit(1);
80 | }
81 | Error.stackTraceLimit = origLimit;
82 | finished();
83 | } else {
84 | // we have no clue where alloy is installed, so we're going to subprocess
85 | // alloy and hope it's in the system path or a well known place
86 | var paths = {};
87 | var locatorCmd = process.platform === 'win32' ? 'where' : 'which';
88 | parallel(this, ['alloy', 'node'].map(function (bin) {
89 | return function (done) {
90 | var envName = 'ALLOY_' + (bin === 'node' ? 'NODE_' : '') + 'PATH';
91 |
92 | paths[bin] = process.env[envName];
93 | if (paths[bin]) {
94 | done();
95 | } else if (process.platform === 'win32' && bin === 'alloy') {
96 | paths.alloy = 'alloy.cmd';
97 | done();
98 | } else {
99 | exec(locatorCmd + ' ' + bin, function (err, stdout, strerr) {
100 | if (!err) {
101 | paths[bin] = stdout.trim();
102 | done();
103 | } else {
104 | parallel(this, [
105 | '/usr/local/bin/' + bin,
106 | '/opt/local/bin/' + bin,
107 | path.join(process.env.HOME, 'local/bin', bin),
108 | '/opt/bin/' + bin,
109 | '/usr/bin/' + bin
110 | ].map(function (p) {
111 | return function (cb) {
112 | if (afs.exists(p)) { paths[bin] = p; }
113 | cb();
114 | };
115 | }), done);
116 | }
117 | });
118 | }
119 | };
120 | }), function () {
121 |
122 | // compose alloy command execution
123 | var cmd = [paths.node, paths.alloy, 'compile', appDir, '--config', config];
124 | if (cli.argv['no-colors'] || cli.argv['color'] === false) { cmd.push('--no-colors'); }
125 |
126 | // process each line of output from alloy
127 | function checkLine(line) {
128 | var re = new RegExp(
129 | '^(?:\u001b\\[\\d+m)?\\[?(' +
130 | logger.getLevels().join('|') +
131 | ')\\]?\s*(?:\u001b\\[\\d+m)?(.*)', 'i'
132 | );
133 | if (line) {
134 | var m = line.match(re);
135 | if (m) {
136 | logger[m[1].toLowerCase()](m[2].trim());
137 | } else {
138 | logger.debug(line);
139 | }
140 | }
141 | }
142 |
143 | // execute alloy in os-specific manner
144 | var child;
145 | if (process.platform === 'win32' && paths.alloy === 'alloy.cmd') {
146 | cmd.shift();
147 | logger.info(__('Executing Alloy compile: %s',
148 | ['cmd','/s','/c'].concat(cmd).join(' ').cyan));
149 |
150 | // arg processing from https://github.com/MarcDiethelm/superspawn
151 | child = spawn('cmd', [['/s', '/c', '"' +
152 | cmd.map(function(a) {
153 | if (/^[^"].* .*[^"]/.test(a)) return '"'+a+'"'; return a;
154 | }).join(" ") + '"'].join(" ")], {
155 | stdio: 'inherit',
156 | windowsVerbatimArguments: true
157 | }
158 | );
159 | } else {
160 | logger.info(__('Executing Alloy compile: %s', cmd.join(' ').cyan));
161 | child = spawn(cmd.shift(), cmd);
162 | child.stdout.on('data', function (data) {
163 | data.toString().split('\n').forEach(checkLine);
164 | });
165 | child.stderr.on('data', function (data) {
166 | data.toString().split('\n').forEach(checkLine);
167 | });
168 | }
169 |
170 | // handle the completion of alloy, success or otherwise
171 | child.on('exit', function (code) {
172 | if (code) {
173 | logger.error(__('Alloy compiler failed'));
174 | process.exit(1);
175 | } else {
176 | logger.info(__('Alloy compiler completed successfully'));
177 |
178 | afs.exists(path.join(cli.argv["project-dir"], 'build', 'i18n')) && process.argv.push('--i18n-dir', 'build');
179 | afs.exists(path.join(cli.argv["project-dir"], 'build', 'platform')) && (cli.argv['platform-dir'] = 'build/platform');
180 | }
181 | finished();
182 | });
183 |
184 | });
185 | }
186 | }
187 |
188 | cli.addHook('build.pre.compile', function (build, finished) {
189 | var deployType = build.deployType,
190 | target = build.target;
191 |
192 | run(build.deviceFamily, deployType, target, finished);
193 | });
194 |
195 | cli.addHook('codeprocessor.pre.run', function (build, finished) {
196 | run('none', 'development', undefined, finished, SILENT);
197 | });
198 | };
199 |
--------------------------------------------------------------------------------
/example/plugins/ti.alloy/hooks/deepclean.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Alloy
3 | * Copyright (c) 2014 by Appcelerator, Inc. All Rights Reserved.
4 | * See LICENSE for more information on licensing.
5 | */
6 |
7 | exports.cliVersion = '>=3.X';
8 | var SILENT = true;
9 |
10 | exports.init = function (logger, config, cli, appc) {
11 | var path = require('path'),
12 | fs = require('fs'),
13 | afs = appc.fs;
14 |
15 | function run(finished) {
16 | if(cli.argv['shallow'] === '') {
17 | logger.info('Not cleaning the Resources directory');
18 | finished();
19 | return;
20 | }
21 | var appDir = path.join(cli.argv['project-dir'], 'app');
22 | if (!afs.exists(appDir)) {
23 | logger.debug('Project not an Alloy app, exiting.');
24 | finished();
25 | return;
26 | }
27 |
28 | var resourcesDir = path.join(cli.argv['project-dir'], 'Resources');
29 | if (!afs.exists(resourcesDir)) {
30 | logger.debug('Resources directory does not exist.');
31 | finished();
32 | return;
33 | }
34 | rmdir(resourcesDir, fs, path, logger);
35 | logger.debug('Resources directory of %s has been emptied', appDir.cyan);
36 | finished();
37 | }
38 |
39 | cli.addHook('clean.post', function (build, finished) {
40 | run(finished);
41 | });
42 |
43 | };
44 |
45 | function rmdir(dirPath, fs, path, logger, removeSelf) {
46 | var files;
47 | try {
48 | files = fs.readdirSync(dirPath);
49 | }
50 | catch(e) {
51 | return;
52 | }
53 | if (files.length > 0) {
54 | for (var i = 0; i < files.length; i++) {
55 | var filePath = path.join(dirPath, files[i]);
56 | if (fs.statSync(filePath).isFile()) {
57 | fs.unlinkSync(filePath);
58 | } else {
59 | rmdir(filePath, fs, path, logger, true);
60 | }
61 | }
62 | }
63 | if (removeSelf) {
64 | fs.rmdirSync(dirPath);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/example/plugins/ti.alloy/plugin.py:
--------------------------------------------------------------------------------
1 | import os, sys, subprocess, hashlib
2 |
3 | import subprocess
4 |
5 | def check_output(*popenargs, **kwargs):
6 | r"""Run command with arguments and return its output as a byte string.
7 |
8 | Backported from Python 2.7 as it's implemented as pure python on stdlib.
9 |
10 | >>> check_output(['/usr/bin/python', '--version'])
11 | Python 2.6.2
12 | """
13 | process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs)
14 | output, unused_err = process.communicate()
15 | retcode = process.poll()
16 | if retcode:
17 | cmd = kwargs.get("args")
18 | if cmd is None:
19 | cmd = popenargs[0]
20 | error = subprocess.CalledProcessError(retcode, cmd)
21 | error.output = output
22 | raise error
23 | return output
24 |
25 | def compile(config):
26 | paths = {}
27 | binaries = ["alloy","node"]
28 |
29 | dotAlloy = os.path.abspath(os.path.join(config['project_dir'], 'build', '.alloynewcli'))
30 | if os.path.exists(dotAlloy):
31 | print "[DEBUG] build/.alloynewcli file found, skipping plugin..."
32 | os.remove(dotAlloy)
33 | else:
34 | for binary in binaries:
35 | try:
36 | # see if the environment variable is defined
37 | paths[binary] = os.environ["ALLOY_" + ("NODE_" if binary == "node" else "") + "PATH"]
38 | except KeyError as ex:
39 | # next try PATH, and then our guess paths
40 | if sys.platform == "darwin" or sys.platform.startswith('linux'):
41 | userPath = os.environ["HOME"]
42 | guessPaths = [
43 | "/usr/local/bin/"+binary,
44 | "/opt/local/bin/"+binary,
45 | userPath+"/local/bin/"+binary,
46 | "/opt/bin/"+binary,
47 | "/usr/bin/"+binary,
48 | "/usr/local/share/npm/bin/"+binary
49 | ]
50 |
51 | try:
52 | binaryPath = check_output(["which",binary], stderr=subprocess.STDOUT).strip()
53 | print "[DEBUG] %s installed at '%s'" % (binary,binaryPath)
54 | except:
55 | print "[WARN] Couldn't find %s on your PATH:" % binary
56 | print "[WARN] %s" % os.environ["PATH"]
57 | print "[WARN]"
58 | print "[WARN] Checking for %s in a few default locations:" % binary
59 | for p in guessPaths:
60 | sys.stdout.write("[WARN] %s -> " % p)
61 | if os.path.exists(p):
62 | binaryPath = p
63 | print "FOUND"
64 | break
65 | else:
66 | print "not found"
67 | binaryPath = None
68 |
69 | if binaryPath is None:
70 | print "[ERROR] Couldn't find %s" % binary
71 | sys.exit(1)
72 | else:
73 | paths[binary] = binaryPath
74 |
75 | # no guesses on windows, just use the PATH
76 | elif sys.platform == "win32":
77 | paths["alloy"] = "alloy.cmd"
78 |
79 | f = os.path.abspath(os.path.join(config['project_dir'], 'app'))
80 | if os.path.exists(f):
81 | print "[INFO] alloy app found at %s" % f
82 | rd = os.path.abspath(os.path.join(config['project_dir'], 'Resources'))
83 |
84 | devicefamily = 'none'
85 | simtype = 'none'
86 | version = '0'
87 | deploytype = 'development'
88 |
89 | if config['platform']==u'ios':
90 | version = config['iphone_version']
91 | devicefamily = config['devicefamily']
92 | deploytype = config['deploytype']
93 | if config['platform']==u'android':
94 | builder = config['android_builder']
95 | version = builder.tool_api_level
96 | deploytype = config['deploy_type']
97 | if config['platform']==u'mobileweb':
98 | builder = config['mobileweb_builder']
99 | deploytype = config['deploytype']
100 |
101 | cfg = "platform=%s,version=%s,simtype=%s,devicefamily=%s,deploytype=%s," % (config['platform'],version,simtype,devicefamily,deploytype)
102 |
103 | if sys.platform == "win32":
104 | cmd = [paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
105 | else:
106 | cmd = [paths["node"], paths["alloy"], "compile", f, "--no-colors", "--config", cfg]
107 |
108 | print "[INFO] Executing Alloy compile:"
109 | print "[INFO] %s" % " ".join(cmd)
110 |
111 | try:
112 | print check_output(cmd, stderr=subprocess.STDOUT)
113 | except subprocess.CalledProcessError as ex:
114 | if hasattr(ex, 'output'):
115 | print ex.output
116 | print "[ERROR] Alloy compile failed"
117 | retcode = 1
118 | if hasattr(ex, 'returncode'):
119 | retcode = ex.returncode
120 | sys.exit(retcode)
121 | except EnvironmentError as ex:
122 | print "[ERROR] Unexpected error with Alloy compiler plugin: %s" % ex.strerror
123 | sys.exit(2)
124 |
--------------------------------------------------------------------------------
/example/tiapp.xml:
--------------------------------------------------------------------------------
1 |
2 | nl.fokkezb.html2as
3 | ti-html2as
4 | 1.0
5 | Fokke Zandbergen
6 | http://fokkezb.nl
7 | HTML to Attributed String parser for Titanium
8 | 2014 Fokke Zandbergen
9 | appicon.png
10 | false
11 | false
12 | true
13 | b567e476-dd2c-47c5-b28b-7fbb2c857431
14 | dp
15 |
16 |
17 |
18 | UISupportedInterfaceOrientations~iphone
19 |
20 | UIInterfaceOrientationPortrait
21 |
22 | UISupportedInterfaceOrientations~ipad
23 |
24 | UIInterfaceOrientationPortrait
25 | UIInterfaceOrientationPortraitUpsideDown
26 | UIInterfaceOrientationLandscapeLeft
27 | UIInterfaceOrientationLandscapeRight
28 |
29 | UIRequiresPersistentWiFi
30 |
31 | UIPrerenderedIcon
32 |
33 | UIStatusBarHidden
34 |
35 | UIStatusBarStyle
36 | UIStatusBarStyleDefault
37 |
38 |
39 |
40 |
41 | nl.fokkezb.html2as
42 |
43 |
44 | false
45 | false
46 | true
47 | true
48 | false
49 |
50 | 5.2.0.GA
51 | ti.alloy
52 |
53 |
54 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var htmlparser = require("htmlparser2");
2 | var entities = require("entities");
3 |
4 | // References to the full namespace to they get packaged for device builds
5 | var references = [
6 | Ti.UI.AttributedString
7 | ];
8 |
9 | var ns = Ti.UI;
10 |
11 | if (parseInt(Ti.version.split('.')[0], 10) < 4) {
12 | references.push(Ti.UI.iOS.AttributedString);
13 | ns = Ti.UI.iOS;
14 | }
15 |
16 | var ios = Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'iOS';
17 |
18 | // Custom matcher is a function to enable parsing of extra node types.
19 | // @return Object
20 | // @property parameters Object
21 | // @property continue Boolean
22 | // @example
23 | // function(node, parameters, outerFont, offset, length, ns) {
24 | // if (node.type === 'tag' && node.name && node.name === 'h1') {
25 | // parameters.attributes.unshift({
26 | // type: ns.ATTRIBUTE_FOREGROUND_COLOR,
27 | // value: Alloy.CFG.h1Color,
28 | // range: [offset, length]
29 | // });
30 | // parameters.attributes.unshift({
31 | // type: ns.ATTRIBUTE_FONT,
32 | // value: {
33 | // fontSize: Alloy.CFG.h1FontSize,
34 | // fontFamily: Alloy.CFG.h1FontFamily
35 | // },
36 | // range: [offset, length]
37 | // });
38 | // }
39 | // return {
40 | // parameters: parameters,
41 | // continue: true
42 | // };
43 | // }
44 | function walker(node, parameters, outerFont, customMatcher) {
45 |
46 | if (node.type === 'text') {
47 | parameters.text += entities.decodeHTML(node.data);
48 |
49 | } else if (node.type === 'tag' && node.children) {
50 | var innerFont;
51 |
52 | // clone font property from wrapping tags
53 | if (outerFont) {
54 | innerFont = {};
55 | outerFont.fontWeight && (innerFont.fontWeight = outerFont.fontWeight);
56 | outerFont.fontFamily && (innerFont.fontFamily = outerFont.fontFamily);
57 | outerFont.fontSize && (innerFont.fontSize = outerFont.fontSize);
58 | }
59 |
60 | // override font properties from this tag
61 | if (node.name === 'strong' || node.name === 'b') {
62 | innerFont || (innerFont = {});
63 | innerFont.fontWeight = 'bold';
64 |
65 | } else if (node.name === 'font' && node.attribs) {
66 |
67 | if (node.attribs.face) {
68 | innerFont || (innerFont = {});
69 | innerFont.fontFamily = node.attribs.face;
70 | }
71 |
72 | if (node.attribs.size) {
73 | innerFont || (innerFont = {});
74 | innerFont.fontSize = node.attribs.size;
75 | }
76 | }
77 |
78 | // save length before children
79 | var offset = parameters.text.length;
80 |
81 | // walk children
82 | node.children.forEach(function onEach(child) {
83 | parameters = walker(child, parameters, innerFont, customMatcher);
84 | });
85 |
86 | // calculate length of (grant)children text nodes
87 | var length = parameters.text.length - offset;
88 |
89 | if (typeof customMatcher === 'function') {
90 | var customMatch = customMatcher(node, parameters, outerFont, offset, length, ns);
91 |
92 | if (customMatch.parameters === undefined || customMatch.continue === undefined) {
93 | throw new Error('customMatcher should return an object with parameters and continue properties defined');
94 | }
95 |
96 | parameters = customMatch.parameters;
97 |
98 | if (customMatch.continue === false) {
99 | return parameters;
100 | }
101 | }
102 |
103 | // only apply attributes if we wrap text
104 | if (length > 0) {
105 |
106 | if (node.name === 'a' && node.attribs && node.attribs.href) {
107 | parameters.attributes.unshift({
108 | type: ns.ATTRIBUTE_LINK,
109 | value: node.attribs.href,
110 | range: [offset, length]
111 | });
112 |
113 | } else if (node.name === 'u') {
114 | parameters.attributes.unshift({
115 | type: ns.ATTRIBUTE_UNDERLINES_STYLE,
116 | value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
117 | range: [offset, length]
118 | });
119 |
120 | } else if (ios && (node.name === 'i' || node.name === 'em')) {
121 | parameters.attributes.unshift({
122 | type: ns.ATTRIBUTE_OBLIQUENESS,
123 | value: 0.25,
124 | range: [offset, length]
125 | });
126 |
127 | } else if (node.name === 'strike' || node.name === 'del' || node.name === 's') {
128 | parameters.attributes.unshift({
129 | type: ns.ATTRIBUTE_STRIKETHROUGH_STYLE,
130 | value: ios ? ns.ATTRIBUTE_UNDERLINE_STYLE_SINGLE : undefined,
131 | range: [offset, length]
132 | });
133 |
134 | } else if (ios && node.name === 'effect') {
135 | parameters.attributes.unshift({
136 | type: ns.ATTRIBUTE_TEXT_EFFECT,
137 | value: ns.ATTRIBUTE_LETTERPRESS_STYLE,
138 | range: [offset, length]
139 | });
140 |
141 | } else if (ios && node.name === 'kern' && node.attribs && node.attribs.value) {
142 | parameters.attributes.unshift({
143 | type: ns.ATTRIBUTE_KERN,
144 | value: node.attribs.value,
145 | range: [offset, length]
146 | });
147 |
148 | } else if (ios && node.name === 'expansion' && node.attribs && node.attribs.value) {
149 | parameters.attributes.unshift({
150 | type: ns.ATTRIBUTE_EXPANSION,
151 | value: node.attribs.value,
152 | range: [offset, length]
153 | });
154 |
155 | } else if (node.name === 'font' && node.attribs && node.attribs.color) {
156 | parameters.attributes.unshift({
157 | type: ns.ATTRIBUTE_FOREGROUND_COLOR,
158 | value: node.attribs.color,
159 | range: [offset, length]
160 | });
161 | }
162 | }
163 |
164 | // if we have a font to set
165 | if (innerFont) {
166 | parameters.attributes.unshift({
167 | type: ns.ATTRIBUTE_FONT,
168 | value: innerFont,
169 | range: [offset, length]
170 | });
171 | }
172 | }
173 |
174 | return parameters;
175 | }
176 |
177 | module.exports = function(html, callback, customMatcher) {
178 | var parser = new htmlparser.Parser(new htmlparser.DomHandler(function(error, dom) {
179 |
180 | if (error) {
181 | callback(error);
182 |
183 | } else {
184 |
185 | var parameters = walker({
186 | type: 'tag',
187 | children: dom
188 | }, {
189 | text: '',
190 | attributes: []
191 | }, null, customMatcher);
192 |
193 | var attr = ns.createAttributedString(parameters);
194 |
195 | callback(null, attr);
196 | }
197 | }));
198 |
199 | // remove newlines
200 | html = html.replace(/[\r\n]+/gm, ' ').replace(/\s+/g, ' ');
201 |
202 | // replace
with newlines
203 | html = html.replace(/
]*>/gm, '\n');
204 |
205 | parser.parseComplete(html);
206 |
207 | };
208 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ti-html2as",
3 | "version": "1.5.0",
4 | "description": "HTML to Attributed String parser for Titanium",
5 | "titaniumManifest": {
6 | "guid": "5be532b5-9e73-4515-bd8e-5472b0bf52f2",
7 | "moduleid": "nl.fokkezb.html2as"
8 | },
9 | "author": {
10 | "name": "Fokke Zandbergen",
11 | "email": "mail@fokkezb.nl",
12 | "url": "http://fokkezb.nl"
13 | },
14 | "license": "LGPL-2.1",
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/fokkezb/ti-html2as.git"
18 | },
19 | "engines": {
20 | "titaniumsdk": ">= 3.3.0"
21 | },
22 | "main": "index.js",
23 | "keywords": [
24 | "titanium",
25 | "titaniumifier",
26 | "htmlparser",
27 | "html",
28 | "parser",
29 | "attributed",
30 | "string"
31 | ],
32 | "scripts": {
33 | "test": "istanbul cover jasmine"
34 | },
35 | "dependencies": {
36 | "entities": "^1.1.1",
37 | "htmlparser2": "^3.8.0"
38 | },
39 | "devDependencies": {
40 | "grunt": "^0.4.2",
41 | "grunt-contrib-clean": "^0.6.0",
42 | "grunt-titanium": "fokkezb/grunt-titanium#patch-3",
43 | "grunt-titaniumifier": "^1.1.0",
44 | "grunt-zip": "^0.16.0",
45 | "istanbul": "^0.4.2",
46 | "jasmine": "^2.4.1",
47 | "proxyquire": "^1.7.4"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jasonkneen/ti-html2as/c5bd034370840da52211beaedcfbe408ea4dcbba/screencast.gif
--------------------------------------------------------------------------------
/spec/support/jasmine.json:
--------------------------------------------------------------------------------
1 | {
2 | "spec_dir": "test/specs",
3 | "spec_files": [
4 | "**/*[sS]pec.js"
5 | ],
6 | "helpers": []
7 | }
8 |
--------------------------------------------------------------------------------
/test/specs/indexSpec.js:
--------------------------------------------------------------------------------
1 | var proxyquire = require('proxyquire').noCallThru();
2 |
3 | describe('html2as - standard functioinality -', function() {
4 | var html2as = null;
5 |
6 | beforeEach(function() {
7 | Ti = require('../spies/TiSpy')();
8 | });
9 |
10 | function init() {
11 | html2as = proxyquire('../../index', {});
12 | }
13 |
14 | afterEach(function() {
15 | delete Ti;
16 | });
17 |
18 | it('correctly parses known html tags to the correct AttributedString values - iOS - Ti 5.2.0', function() {
19 | Ti.version = '5.2.0';
20 |
21 | Ti.Platform.name = 'iPhone OS';
22 |
23 | init();
24 |
25 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
26 | return parameters;
27 | });
28 |
29 | var callbackExecuted = false;
30 | var callback = function(err, result) {
31 | expect(err).toBeNull();
32 | expect(result).toEqual({
33 | text: 'Strong Text\nBold Text\nUnderlined Text\nEmphasised Text\nItalicised Text\nStruck (strike) Text\nDeleted Text\nStruck (s) Text\nArial Text\nFont size 12 Text\nRed Text\nAnchor\nLetterpress Text\nKerned Text\nExpanded Text',
34 | attributes: [{
35 | type: 20,
36 | value: '0.5',
37 | range: [194, 13]
38 | }, {
39 | type: 5,
40 | value: '10',
41 | range: [182, 11]
42 | }, {
43 | type: 13,
44 | value: '_UIKitNewLetterpressStyle',
45 | range: [165, 16]
46 | }, {
47 | type: 15,
48 | value: 'https://github.com/FokkeZB/ti-html2as',
49 | range: [158, 6]
50 | }, {
51 | type: 2,
52 | value: 'red',
53 | range: [149, 8]
54 | }, {
55 | type: 0,
56 | value: {
57 | fontSize: '12'
58 | },
59 | range: [131, 17]
60 | }, {
61 | type: 0,
62 | value: {
63 | fontFamily: 'Arial'
64 | },
65 | range: [120, 10]
66 | }, {
67 | type: 6,
68 | value: 1,
69 | range: [104, 15]
70 | }, {
71 | type: 6,
72 | value: 1,
73 | range: [91, 12]
74 | }, {
75 | type: 6,
76 | value: 1,
77 | range: [70, 20]
78 | }, {
79 | type: 19,
80 | value: 0.25,
81 | range: [54, 15]
82 | }, {
83 | type: 19,
84 | value: 0.25,
85 | range: [38, 15]
86 | }, {
87 | type: 7,
88 | value: 1,
89 | range: [22, 15]
90 | }, {
91 | type: 0,
92 | value: {
93 | fontWeight: 'bold'
94 | },
95 | range: [12, 9]
96 | }, {
97 | type: 0,
98 | value: {
99 | fontWeight: 'bold'
100 | },
101 | range: [0, 11]
102 | }]
103 | });
104 |
105 | callbackExecuted = true;
106 | };
107 |
108 | var html = 'Strong Text
' +
109 | 'Bold Text
' +
110 | 'Underlined Text
' +
111 | 'Emphasised Text
' +
112 | 'Italicised Text
' +
113 | 'Struck (strike) Text
' +
114 | 'Deleted Text
' +
115 | 'Struck (s) Text
' +
116 | 'Arial Text
' +
117 | 'Font size 12 Text
' +
118 | 'Red Text
' +
119 | 'Anchor
' +
120 | 'Letterpress Text
' +
121 | 'Kerned Text
' +
122 | 'Expanded Text';
123 |
124 | html2as(html, callback);
125 |
126 | expect(callbackExecuted).toBeTruthy();
127 | });
128 |
129 | it('correctly parses known html tags to the correct AttributedString values - iOS - Ti 3.5.1', function() {
130 | Ti.version = '3.5.1';
131 |
132 | Ti.Platform.name = 'iPhone OS';
133 |
134 | init();
135 |
136 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
137 | return parameters;
138 | });
139 |
140 | var callbackExecuted = false;
141 | var callback = function(err, result) {
142 | expect(err).toBeNull();
143 | expect(result).toEqual({
144 | text: 'Strong Text\nBold Text\nUnderlined Text\nEmphasised Text\nItalicised Text\nStruck (strike) Text\nDeleted Text\nStruck (s) Text\nArial Text\nFont size 12 Text\nRed Text\nAnchor\nLetterpress Text\nKerned Text\nExpanded Text',
145 | attributes: [{
146 | type: 20,
147 | value: '0.5',
148 | range: [194, 13]
149 | }, {
150 | type: 5,
151 | value: '10',
152 | range: [182, 11]
153 | }, {
154 | type: 13,
155 | value: '_UIKitNewLetterpressStyle',
156 | range: [165, 16]
157 | }, {
158 | type: 15,
159 | value: 'https://github.com/FokkeZB/ti-html2as',
160 | range: [158, 6]
161 | }, {
162 | type: 2,
163 | value: 'red',
164 | range: [149, 8]
165 | }, {
166 | type: 0,
167 | value: {
168 | fontSize: '12'
169 | },
170 | range: [131, 17]
171 | }, {
172 | type: 0,
173 | value: {
174 | fontFamily: 'Arial'
175 | },
176 | range: [120, 10]
177 | }, {
178 | type: 6,
179 | value: 1,
180 | range: [104, 15]
181 | }, {
182 | type: 6,
183 | value: 1,
184 | range: [91, 12]
185 | }, {
186 | type: 6,
187 | value: 1,
188 | range: [70, 20]
189 | }, {
190 | type: 19,
191 | value: 0.25,
192 | range: [54, 15]
193 | }, {
194 | type: 19,
195 | value: 0.25,
196 | range: [38, 15]
197 | }, {
198 | type: 7,
199 | value: 1,
200 | range: [22, 15]
201 | }, {
202 | type: 0,
203 | value: {
204 | fontWeight: 'bold'
205 | },
206 | range: [12, 9]
207 | }, {
208 | type: 0,
209 | value: {
210 | fontWeight: 'bold'
211 | },
212 | range: [0, 11]
213 | }]
214 | });
215 |
216 | callbackExecuted = true;
217 | };
218 |
219 | var html = 'Strong Text
' +
220 | 'Bold Text
' +
221 | 'Underlined Text
' +
222 | 'Emphasised Text
' +
223 | 'Italicised Text
' +
224 | 'Struck (strike) Text
' +
225 | 'Deleted Text
' +
226 | 'Struck (s) Text
' +
227 | 'Arial Text
' +
228 | 'Font size 12 Text
' +
229 | 'Red Text
' +
230 | 'Anchor
' +
231 | 'Letterpress Text
' +
232 | 'Kerned Text
' +
233 | 'Expanded Text';
234 |
235 | html2as(html, callback);
236 |
237 | expect(callbackExecuted).toBeTruthy();
238 | });
239 |
240 | it('correctly parses known html tags to the correct AttributedString values - Android - Ti 5.2.0', function() {
241 | Ti.version = '5.2.0';
242 |
243 | Ti.Platform.name = 'Android OS';
244 |
245 | init();
246 |
247 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
248 | return parameters;
249 | });
250 |
251 | var callbackExecuted = false;
252 | var callback = function(err, result) {
253 | expect(err).toBeNull();
254 | expect(result).toEqual({
255 | text: 'Strong Text\nBold Text\nUnderlined Text\nEmphasised Text\nItalicised Text\nStruck (strike) Text\nDeleted Text\nStruck (s) Text\nArial Text\nFont size 12 Text\nRed Text\nAnchor\nLetterpress Text\nKerned Text\nExpanded Text',
256 | attributes: [{
257 | type: 15,
258 | value: 'https://github.com/FokkeZB/ti-html2as',
259 | range: [158, 6]
260 | }, {
261 | type: 2,
262 | value: 'red',
263 | range: [149, 8]
264 | }, {
265 | type: 0,
266 | value: {
267 | fontSize: '12'
268 | },
269 | range: [131, 17]
270 | }, {
271 | type: 0,
272 | value: {
273 | fontFamily: 'Arial'
274 | },
275 | range: [120, 10]
276 | }, {
277 | type: 6,
278 | value: undefined,
279 | range: [104, 15]
280 | }, {
281 | type: 6,
282 | value: undefined,
283 | range: [91, 12]
284 | }, {
285 | type: 6,
286 | value: undefined,
287 | range: [70, 20]
288 | }, {
289 | type: 7,
290 | value: undefined,
291 | range: [22, 15]
292 | }, {
293 | type: 0,
294 | value: {
295 | fontWeight: 'bold'
296 | },
297 | range: [12, 9]
298 | }, {
299 | type: 0,
300 | value: {
301 | fontWeight: 'bold'
302 | },
303 | range: [0, 11]
304 | }]
305 | });
306 |
307 | callbackExecuted = true;
308 | };
309 |
310 | var html = 'Strong Text
' +
311 | 'Bold Text
' +
312 | 'Underlined Text
' +
313 | 'Emphasised Text
' +
314 | 'Italicised Text
' +
315 | 'Struck (strike) Text
' +
316 | 'Deleted Text
' +
317 | 'Struck (s) Text
' +
318 | 'Arial Text
' +
319 | 'Font size 12 Text
' +
320 | 'Red Text
' +
321 | 'Anchor
' +
322 | 'Letterpress Text
' +
323 | 'Kerned Text
' +
324 | 'Expanded Text';
325 |
326 | html2as(html, callback);
327 |
328 | expect(callbackExecuted).toBeTruthy();
329 | });
330 |
331 | it('correctly parses known html tags to the correct AttributedString values - nested tags - Android - Ti 5.2.0', function() {
332 | Ti.version = '5.2.0';
333 |
334 | Ti.Platform.name = 'Android OS';
335 |
336 | init();
337 |
338 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
339 | return parameters;
340 | });
341 |
342 | var callbackExecuted = false;
343 | var callback = function(err, result) {
344 | expect(err).toBeNull();
345 | expect(result).toEqual({
346 | text: 'Strong Text with some Italicised Text and some Underlined Text',
347 | attributes: [{
348 | type: 0,
349 | value: {
350 | fontWeight: 'bold'
351 | },
352 | range: [0, 62]
353 | }, {
354 | type: 0,
355 | value: {
356 | fontWeight: 'bold'
357 | },
358 | range: [47, 15]
359 | }, {
360 | type: 7,
361 | value: undefined,
362 | range: [47, 15]
363 | }, {
364 | type: 0,
365 | value: {
366 | fontWeight: 'bold'
367 | },
368 | range: [22, 15]
369 | }]
370 | });
371 |
372 | callbackExecuted = true;
373 | };
374 |
375 | var html = 'Strong Text with some Italicised Text and some Underlined Text';
376 |
377 | html2as(html, callback);
378 |
379 | expect(callbackExecuted).toBeTruthy();
380 | });
381 | });
382 |
383 | describe('html2as - custom functioinality -', function() {
384 | var html2as = null;
385 |
386 | beforeEach(function() {
387 | Ti = require('../spies/TiSpy')();
388 | });
389 |
390 | function init() {
391 | html2as = proxyquire('../../index', {});
392 | }
393 |
394 | afterEach(function() {
395 | delete Ti;
396 | });
397 |
398 | it('correctly parses known html tags and custom html tags to the correct AttributedString values - iOS - Ti 5.2.0', function() {
399 | Ti.version = '5.2.0';
400 |
401 | Ti.Platform.name = 'iPhone OS';
402 |
403 | init();
404 |
405 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
406 | return parameters;
407 | });
408 |
409 | var customMatcher = function(node, parameters, outerFont, offset, length, ns) {
410 | if (node.type === 'tag' && node.name && node.name === 'h1') {
411 | parameters.attributes.unshift({
412 | type: ns.ATTRIBUTE_FOREGROUND_COLOR,
413 | value: 'red',
414 | range: [offset, length]
415 | });
416 | parameters.attributes.unshift({
417 | type: ns.ATTRIBUTE_FONT,
418 | value: {
419 | fontSize: 48,
420 | fontFamily: 'Arial'
421 | },
422 | range: [offset, length]
423 | });
424 | }
425 |
426 | return {
427 | parameters: parameters,
428 | continue: true
429 | };
430 | };
431 |
432 | var callbackExecuted = false;
433 | var callback = function(err, result) {
434 | expect(err).toBeNull();
435 | expect(result).toEqual({
436 | text: 'Strong Text\nBold Text\nUnderlined Text\nEmphasised Text\nItalicised Text\nStruck (strike) Text\nDeleted Text\nStruck (s) Text\nArial Text\nFont size 12 Text\nRed Text\nAnchor\nLetterpress Text\nKerned Text\nExpanded Text\nH1 Text',
437 | attributes: [{
438 | type: 0,
439 | value: {
440 | fontSize: 48,
441 | fontFamily: 'Arial'
442 | },
443 | range: [208, 7]
444 | }, {
445 | type: 2,
446 | value: 'red',
447 | range: [208, 7]
448 | }, {
449 | type: 20,
450 | value: '0.5',
451 | range: [194, 13]
452 | }, {
453 | type: 5,
454 | value: '10',
455 | range: [182, 11]
456 | }, {
457 | type: 13,
458 | value: '_UIKitNewLetterpressStyle',
459 | range: [165, 16]
460 | }, {
461 | type: 15,
462 | value: 'https://github.com/FokkeZB/ti-html2as',
463 | range: [158, 6]
464 | }, {
465 | type: 2,
466 | value: 'red',
467 | range: [149, 8]
468 | }, {
469 | type: 0,
470 | value: {
471 | fontSize: '12'
472 | },
473 | range: [131, 17]
474 | }, {
475 | type: 0,
476 | value: {
477 | fontFamily: 'Arial'
478 | },
479 | range: [120, 10]
480 | }, {
481 | type: 6,
482 | value: 1,
483 | range: [104, 15]
484 | }, {
485 | type: 6,
486 | value: 1,
487 | range: [91, 12]
488 | }, {
489 | type: 6,
490 | value: 1,
491 | range: [70, 20]
492 | }, {
493 | type: 19,
494 | value: 0.25,
495 | range: [54, 15]
496 | }, {
497 | type: 19,
498 | value: 0.25,
499 | range: [38, 15]
500 | }, {
501 | type: 7,
502 | value: 1,
503 | range: [22, 15]
504 | }, {
505 | type: 0,
506 | value: {
507 | fontWeight: 'bold'
508 | },
509 | range: [12, 9]
510 | }, {
511 | type: 0,
512 | value: {
513 | fontWeight: 'bold'
514 | },
515 | range: [0, 11]
516 | }]
517 | });
518 |
519 | callbackExecuted = true;
520 | };
521 |
522 | var html = 'Strong Text
' +
523 | 'Bold Text
' +
524 | 'Underlined Text
' +
525 | 'Emphasised Text
' +
526 | 'Italicised Text
' +
527 | 'Struck (strike) Text
' +
528 | 'Deleted Text
' +
529 | 'Struck (s) Text
' +
530 | 'Arial Text
' +
531 | 'Font size 12 Text
' +
532 | 'Red Text
' +
533 | 'Anchor
' +
534 | 'Letterpress Text
' +
535 | 'Kerned Text
' +
536 | 'Expanded Text
' +
537 | 'H1 Text
';
538 |
539 | html2as(html, callback, customMatcher);
540 |
541 | expect(callbackExecuted).toBeTruthy();
542 | });
543 |
544 | it('correctly parses custom html tags, without continuing, to the correct AttributedString values - iOS - Ti 5.2.0', function() {
545 | Ti.version = '5.2.0';
546 |
547 | Ti.Platform.name = 'iPhone OS';
548 |
549 | init();
550 |
551 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
552 | return parameters;
553 | });
554 |
555 | var customMatcher = function(node, parameters, outerFont, offset, length, ns) {
556 |
557 | if (node.type === 'tag' && node.name && node.name !== 'h1') {
558 | parameters.attributes.unshift({
559 | type: ns.ATTRIBUTE_FOREGROUND_COLOR,
560 | value: 'red',
561 | range: [offset, length]
562 | });
563 | }
564 |
565 | return {
566 | parameters: parameters,
567 | continue: false
568 | };
569 | };
570 |
571 | var callbackExecuted = false;
572 | var callback = function(err, result) {
573 | expect(err).toBeNull();
574 | expect(result).toEqual({
575 | text: 'Strong Text\nBold Text\nUnderlined Text\nEmphasised Text\nItalicised Text\nStruck (strike) Text\nDeleted Text\nStruck (s) Text\nArial Text\nFont size 12 Text\nRed Text\nAnchor\nLetterpress Text\nKerned Text\nExpanded Text\nH1 Text',
576 | attributes: [{
577 | type: 2,
578 | value: 'red',
579 | range: [194, 13]
580 | }, {
581 | type: 2,
582 | value: 'red',
583 | range: [182, 11]
584 | }, {
585 | type: 2,
586 | value: 'red',
587 | range: [165, 16]
588 | }, {
589 | type: 2,
590 | value: 'red',
591 | range: [158, 6]
592 | }, {
593 | type: 2,
594 | value: 'red',
595 | range: [149, 8]
596 | }, {
597 | type: 2,
598 | value: 'red',
599 | range: [131, 17]
600 | }, {
601 | type: 2,
602 | value: 'red',
603 | range: [120, 10]
604 | }, {
605 | type: 2,
606 | value: 'red',
607 | range: [104, 15]
608 | }, {
609 | type: 2,
610 | value: 'red',
611 | range: [91, 12]
612 | }, {
613 | type: 2,
614 | value: 'red',
615 | range: [70, 20]
616 | }, {
617 | type: 2,
618 | value: 'red',
619 | range: [54, 15]
620 | }, {
621 | type: 2,
622 | value: 'red',
623 | range: [38, 15]
624 | }, {
625 | type: 2,
626 | value: 'red',
627 | range: [22, 15]
628 | }, {
629 | type: 2,
630 | value: 'red',
631 | range: [12, 9]
632 | }, {
633 | type: 2,
634 | value: 'red',
635 | range: [0, 11]
636 | }]
637 | });
638 |
639 | callbackExecuted = true;
640 | };
641 |
642 | var html = 'Strong Text
' +
643 | 'Bold Text
' +
644 | 'Underlined Text
' +
645 | 'Emphasised Text
' +
646 | 'Italicised Text
' +
647 | 'Struck (strike) Text
' +
648 | 'Deleted Text
' +
649 | 'Struck (s) Text
' +
650 | 'Arial Text
' +
651 | 'Font size 12 Text
' +
652 | 'Red Text
' +
653 | 'Anchor
' +
654 | 'Letterpress Text
' +
655 | 'Kerned Text
' +
656 | 'Expanded Text
' +
657 | 'H1 Text
';
658 |
659 | html2as(html, callback, customMatcher);
660 |
661 | expect(callbackExecuted).toBeTruthy();
662 | });
663 |
664 | it('throws an error for a custom matcher which doesn\'t return the required parameters - iOS - Ti 5.2.0', function() {
665 | Ti.version = '5.2.0';
666 |
667 | Ti.Platform.name = 'iPhone OS';
668 |
669 | init();
670 |
671 | Ti.UI.createAttributedString.and.callFake(function(parameters) {
672 | return parameters;
673 | });
674 |
675 | var customMatcher = function(node, parameters, outerFont, ns) {
676 | return false;
677 | };
678 |
679 | var callbackExecuted = false;
680 | var callback = function() {
681 | callbackExecuted = true;
682 | };
683 |
684 | var html = 'Strong Text
' +
685 | 'Bold Text
' +
686 | 'Underlined Text
' +
687 | 'Emphasised Text
' +
688 | 'Italicised Text
' +
689 | 'Struck (strike) Text
' +
690 | 'Deleted Text
' +
691 | 'Struck (s) Text
' +
692 | 'Arial Text
' +
693 | 'Font size 12 Text
' +
694 | 'Red Text
' +
695 | 'Anchor
' +
696 | 'Letterpress Text
' +
697 | 'Kerned Text
' +
698 | 'Expanded Text
' +
699 | 'H1 Text
';
700 |
701 | expect(function() {
702 | html2as(html, callback, customMatcher);
703 | }).toThrowError('customMatcher should return an object with parameters and continue properties defined');
704 |
705 | expect(callbackExecuted).toBeFalsy();
706 | });
707 | });
708 |
--------------------------------------------------------------------------------
/test/spies/TiSpy.js:
--------------------------------------------------------------------------------
1 | module.exports = function() {
2 | var TiSpy = jasmine.createSpyObj('TiSpy', [
3 | 'addEventListener',
4 | 'applyProperties',
5 | 'createBuffer',
6 | 'createProxy',
7 | 'fireEvent',
8 | 'getApiName',
9 | 'getBubbleParent',
10 | 'getBuildDate',
11 | 'getBuildHash',
12 | 'getLifecycleContainer',
13 | 'getUserAgent',
14 | 'getVersion',
15 | 'removeEventListener',
16 | 'setBubbleParent',
17 | 'setLifecycleContainer',
18 | 'setUserAgent'
19 | ]);
20 |
21 | TiSpy.version = '5.2.0';
22 | TiSpy.Platform = {
23 | name: 'iPhone OS'
24 | };
25 | TiSpy.iOS = {};
26 |
27 | var TiUISpy = jasmine.createSpyObj('TiUISpy', [
28 | 'addEventListener',
29 | 'applyProperties',
30 | 'convertUnits',
31 | 'create2DMatrix',
32 | 'create3DMatrix',
33 | 'createActivityIndicator',
34 | 'createAlertDialog',
35 | 'createAnimation',
36 | 'createAttributedString',
37 | 'createButton',
38 | 'createButtonBar',
39 | 'createDashboardItem',
40 | 'createDashboardView',
41 | 'createEmailDialog',
42 | 'createImageView',
43 | 'createLabel',
44 | 'createListSection',
45 | 'createListView',
46 | 'createMaskedImage',
47 | 'createNotification',
48 | 'createOptionDialog',
49 | 'createPicker',
50 | 'createPickerColumn',
51 | 'createPickerRow',
52 | 'createProgressBar',
53 | 'createRefreshControl',
54 | 'createScrollView',
55 | 'createScrollableView',
56 | 'createSearchBar',
57 | 'createSlider',
58 | 'createSwitch',
59 | 'createTab',
60 | 'createTabGroup',
61 | 'createTableView',
62 | 'createTableViewRow',
63 | 'createTableViewSection',
64 | 'createTextArea',
65 | 'createTextField',
66 | 'createView',
67 | 'createWebView',
68 | 'createWindow',
69 | 'fireEvent',
70 | 'getApiName',
71 | 'getBackgroundColor',
72 | 'getBackgroundImage',
73 | 'getBubbleParent',
74 | 'getCurrentTab',
75 | 'getCurrentWindow',
76 | 'getLifecycleContainer',
77 | 'removeEventListener',
78 | 'setBackgroundColor',
79 | 'setBackgroundImage',
80 | 'setBubbleParent',
81 | 'setCurrentTab',
82 | 'setLifecycleContainer'
83 | ]);
84 |
85 | TiUISpy.ANIMATION_CURVE_EASE_IN = 65536;
86 | TiUISpy.ANIMATION_CURVE_EASE_IN_OUT = 0;
87 | TiUISpy.ANIMATION_CURVE_EASE_OUT = 131072;
88 | TiUISpy.ANIMATION_CURVE_LINEAR = 196608;
89 | TiUISpy.ATTRIBUTE_BACKGROUND_COLOR = 3;
90 | TiUISpy.ATTRIBUTE_BASELINE_OFFSET = 16;
91 | TiUISpy.ATTRIBUTE_EXPANSION = 20;
92 | TiUISpy.ATTRIBUTE_FONT = 0;
93 | TiUISpy.ATTRIBUTE_FOREGROUND_COLOR = 2;
94 | TiUISpy.ATTRIBUTE_KERN = 5;
95 | TiUISpy.ATTRIBUTE_LETTERPRESS_STYLE = '_UIKitNewLetterpressStyle';
96 | TiUISpy.ATTRIBUTE_LIGATURE = 4;
97 | TiUISpy.ATTRIBUTE_LINE_BREAK = 21;
98 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_CHAR_WRAPPING = 1;
99 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_CLIPPING = 2;
100 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_TRUNCATING_HEAD = 3;
101 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_TRUNCATING_MIDDLE = 5;
102 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_TRUNCATING_TAIL = 4;
103 | TiUISpy.ATTRIBUTE_LINE_BREAK_BY_WORD_WRAPPING = 0;
104 | TiUISpy.ATTRIBUTE_LINK = 15;
105 | TiUISpy.ATTRIBUTE_OBLIQUENESS = 19;
106 | TiUISpy.ATTRIBUTE_SHADOW = 10;
107 | TiUISpy.ATTRIBUTE_STRIKETHROUGH_COLOR = 18;
108 | TiUISpy.ATTRIBUTE_STRIKETHROUGH_STYLE = 6;
109 | TiUISpy.ATTRIBUTE_STROKE_COLOR = 8;
110 | TiUISpy.ATTRIBUTE_STROKE_WIDTH = 9;
111 | TiUISpy.ATTRIBUTE_TEXT_EFFECT = 13;
112 | TiUISpy.ATTRIBUTE_UNDERLINES_STYLE = 7;
113 | TiUISpy.ATTRIBUTE_UNDERLINE_BY_WORD = 32768;
114 | TiUISpy.ATTRIBUTE_UNDERLINE_COLOR = 17;
115 | TiUISpy.ATTRIBUTE_UNDERLINE_PATTERN_DASH = 512;
116 | TiUISpy.ATTRIBUTE_UNDERLINE_PATTERN_DASH_DOT = 768;
117 | TiUISpy.ATTRIBUTE_UNDERLINE_PATTERN_DASH_DOT_DOT = 1024;
118 | TiUISpy.ATTRIBUTE_UNDERLINE_PATTERN_DOT = 256;
119 | TiUISpy.ATTRIBUTE_UNDERLINE_PATTERN_SOLID = 0;
120 | TiUISpy.ATTRIBUTE_UNDERLINE_STYLE_DOUBLE = 9;
121 | TiUISpy.ATTRIBUTE_UNDERLINE_STYLE_NONE = 0;
122 | TiUISpy.ATTRIBUTE_UNDERLINE_STYLE_SINGLE = 1;
123 | TiUISpy.ATTRIBUTE_UNDERLINE_STYLE_THICK = 2;
124 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION = 12;
125 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION_EMBEDDING = 0;
126 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION_LEFT_TO_RIGHT = 0;
127 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION_NATURAL = -1;
128 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION_OVERRIDE = 2;
129 | TiUISpy.ATTRIBUTE_WRITING_DIRECTION_RIGHT_TO_LEFT = 1;
130 | TiUISpy.AUTOLINK_ALL = 18446744073709552000;
131 | TiUISpy.AUTOLINK_CALENDAR = 8;
132 | TiUISpy.AUTOLINK_EMAIL_ADDRESSES = 2;
133 | TiUISpy.AUTOLINK_MAP_ADDRESSES = 4;
134 | TiUISpy.AUTOLINK_NONE = 0;
135 | TiUISpy.AUTOLINK_PHONE_NUMBERS = 1;
136 | TiUISpy.AUTOLINK_URLS = 2;
137 | TiUISpy.EXTEND_EDGE_ALL = 15;
138 | TiUISpy.EXTEND_EDGE_BOTTOM = 4;
139 | TiUISpy.EXTEND_EDGE_LEFT = 2;
140 | TiUISpy.EXTEND_EDGE_NONE = 0;
141 | TiUISpy.EXTEND_EDGE_RIGHT = 8;
142 | TiUISpy.EXTEND_EDGE_TOP = 1;
143 | TiUISpy.FACE_DOWN = 6;
144 | TiUISpy.FACE_UP = 5;
145 | TiUISpy.FILL = 'FILL';
146 | TiUISpy.INPUT_BORDERSTYLE_BEZEL = 2;
147 | TiUISpy.INPUT_BORDERSTYLE_LINE = 1;
148 | TiUISpy.INPUT_BORDERSTYLE_NONE = 0;
149 | TiUISpy.INPUT_BORDERSTYLE_ROUNDED = 3;
150 | TiUISpy.INPUT_BUTTONMODE_ALWAYS = 3;
151 | TiUISpy.INPUT_BUTTONMODE_NEVER = 0;
152 | TiUISpy.INPUT_BUTTONMODE_ONBLUR = 2;
153 | TiUISpy.INPUT_BUTTONMODE_ONFOCUS = 1;
154 | TiUISpy.INPUT_TYPE_CLASS_NUMBER = undefined;
155 | TiUISpy.INPUT_TYPE_CLASS_TEXT = undefined;
156 | TiUISpy.KEYBOARD_APPEARANCE_DARK = 1;
157 | TiUISpy.KEYBOARD_APPEARANCE_DEFAULT = 0;
158 | TiUISpy.KEYBOARD_APPEARANCE_LIGHT = 2;
159 | TiUISpy.KEYBOARD_TYPE_ASCII = 1;
160 | TiUISpy.KEYBOARD_TYPE_DECIMAL_PAD = 8;
161 | TiUISpy.KEYBOARD_TYPE_DEFAULT = 0;
162 | TiUISpy.KEYBOARD_TYPE_EMAIL = 7;
163 | TiUISpy.KEYBOARD_TYPE_NAMEPHONE_PAD = 6;
164 | TiUISpy.KEYBOARD_TYPE_NUMBERS_PUNCTUATION = 2;
165 | TiUISpy.KEYBOARD_TYPE_NUMBER_PAD = 4;
166 | TiUISpy.KEYBOARD_TYPE_PHONE_PAD = 5;
167 | TiUISpy.KEYBOARD_TYPE_TWITTER = 9;
168 | TiUISpy.KEYBOARD_TYPE_URL = 3;
169 | TiUISpy.KEYBOARD_TYPE_WEBSEARCH = 10;
170 | TiUISpy.LANDSCAPE_LEFT = 4;
171 | TiUISpy.LANDSCAPE_RIGHT = 3;
172 | TiUISpy.LIST_ACCESSORY_TYPE_CHECKMARK = 3;
173 | TiUISpy.LIST_ACCESSORY_TYPE_DETAIL = 2;
174 | TiUISpy.LIST_ACCESSORY_TYPE_DISCLOSURE = 1;
175 | TiUISpy.LIST_ACCESSORY_TYPE_NONE = 0;
176 | TiUISpy.LIST_ITEM_TEMPLATE_CONTACTS = 2;
177 | TiUISpy.LIST_ITEM_TEMPLATE_DEFAULT = 0;
178 | TiUISpy.LIST_ITEM_TEMPLATE_SETTINGS = 1;
179 | TiUISpy.LIST_ITEM_TEMPLATE_SUBTITLE = 3;
180 | TiUISpy.NOTIFICATION_DURATION_LONG = undefined;
181 | TiUISpy.NOTIFICATION_DURATION_SHORT = undefined;
182 | TiUISpy.PICKER_TYPE_COUNT_DOWN_TIMER = 3;
183 | TiUISpy.PICKER_TYPE_DATE = 1;
184 | TiUISpy.PICKER_TYPE_DATE_AND_TIME = 2;
185 | TiUISpy.PICKER_TYPE_PLAIN = -1;
186 | TiUISpy.PICKER_TYPE_TIME = 0;
187 | TiUISpy.PORTRAIT = 1;
188 | TiUISpy.RETURNKEY_CONTINUE = 11;
189 | TiUISpy.RETURNKEY_DEFAULT = 0;
190 | TiUISpy.RETURNKEY_DONE = 9;
191 | TiUISpy.RETURNKEY_EMERGENCY_CALL = 10;
192 | TiUISpy.RETURNKEY_GO = 1;
193 | TiUISpy.RETURNKEY_GOOGLE = 2;
194 | TiUISpy.RETURNKEY_JOIN = 3;
195 | TiUISpy.RETURNKEY_NEXT = 4;
196 | TiUISpy.RETURNKEY_ROUTE = 5;
197 | TiUISpy.RETURNKEY_SEARCH = 6;
198 | TiUISpy.RETURNKEY_SEND = 7;
199 | TiUISpy.RETURNKEY_YAHOO = 8;
200 | TiUISpy.SIZE = 'SIZE';
201 | TiUISpy.TABLE_VIEW_SEPARATOR_STYLE_NONE = undefined;
202 | TiUISpy.TABLE_VIEW_SEPARATOR_STYLE_SINGLE_LINE = undefined;
203 | TiUISpy.TEXT_ALIGNMENT_CENTER = 1;
204 | TiUISpy.TEXT_ALIGNMENT_LEFT = 0;
205 | TiUISpy.TEXT_ALIGNMENT_RIGHT = 2;
206 | TiUISpy.TEXT_AUTOCAPITALIZATION_ALL = 3;
207 | TiUISpy.TEXT_AUTOCAPITALIZATION_NONE = 0;
208 | TiUISpy.TEXT_AUTOCAPITALIZATION_SENTENCES = 2;
209 | TiUISpy.TEXT_AUTOCAPITALIZATION_WORDS = 1;
210 | TiUISpy.TEXT_ELLIPSIZE_TRUNCATE_END = 4;
211 | TiUISpy.TEXT_ELLIPSIZE_TRUNCATE_MARQUEE = undefined;
212 | TiUISpy.TEXT_ELLIPSIZE_TRUNCATE_MIDDLE = 5;
213 | TiUISpy.TEXT_ELLIPSIZE_TRUNCATE_START = 3;
214 | TiUISpy.TEXT_STYLE_BODY = 'UICTFontTextStyleBody';
215 | TiUISpy.TEXT_STYLE_CAPTION1 = 'UICTFontTextStyleCaption1';
216 | TiUISpy.TEXT_STYLE_CAPTION2 = 'UICTFontTextStyleCaption2';
217 | TiUISpy.TEXT_STYLE_FOOTNOTE = 'UICTFontTextStyleFootnote';
218 | TiUISpy.TEXT_STYLE_HEADLINE = 'UICTFontTextStyleHeadline';
219 | TiUISpy.TEXT_STYLE_SUBHEADLINE = 'UICTFontTextStyleSubhead';
220 | TiUISpy.TEXT_VERTICAL_ALIGNMENT_BOTTOM = 2;
221 | TiUISpy.TEXT_VERTICAL_ALIGNMENT_CENTER = 0;
222 | TiUISpy.TEXT_VERTICAL_ALIGNMENT_TOP = 1;
223 | TiUISpy.UNIT_CM = 'cm';
224 | TiUISpy.UNIT_DIP = 'dip';
225 | TiUISpy.UNIT_IN = 'in';
226 | TiUISpy.UNIT_MM = 'mm';
227 | TiUISpy.UNIT_PX = 'px';
228 | TiUISpy.UNKNOWN = 0;
229 | TiUISpy.UPSIDE_PORTRAIT = 2;
230 | TiUISpy.URL_ERROR_AUTHENTICATION = -1013;
231 | TiUISpy.URL_ERROR_BAD_URL = -1000;
232 | TiUISpy.URL_ERROR_CONNECT = -1004;
233 | TiUISpy.URL_ERROR_FILE = -3001;
234 | TiUISpy.URL_ERROR_FILE_NOT_FOUND = -1100;
235 | TiUISpy.URL_ERROR_HOST_LOOKUP = -1003;
236 | TiUISpy.URL_ERROR_REDIRECT_LOOP = -1007;
237 | TiUISpy.URL_ERROR_SSL_FAILED = -1200;
238 | TiUISpy.URL_ERROR_TIMEOUT = -1001;
239 | TiUISpy.URL_ERROR_UNKNOWN = -1;
240 | TiUISpy.URL_ERROR_UNSUPPORTED_SCHEME = -1002;
241 |
242 | TiUISpy.AttributedString = '';
243 |
244 | TiSpy.UI = TiUISpy;
245 | TiSpy.UI.iOS = TiUISpy;
246 |
247 | return TiSpy;
248 | };
249 |
--------------------------------------------------------------------------------