react-native-to-xml-converter 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,210 @@
1
+ # React Native to XML Converter
2
+
3
+ A powerful CLI tool that converts React Native components to Android XML layouts. This tool parses your React Native JSX/TSX files and generates corresponding Android XML layout files, making it easy to port React Native components to native Android.
4
+
5
+ ## Features
6
+
7
+ - 🔄 **Automatic Conversion**: Converts React Native components to Android XML
8
+ - 🎨 **Style Mapping**: Maps React Native styles to Android attributes
9
+ - 📦 **Component Support**: Supports common RN components (View, Text, ScrollView, etc.)
10
+ - 🚀 **CLI Tool**: Easy-to-use command-line interface
11
+ - 📁 **Organized Output**: Generates XML files in a dedicated `xml/` folder
12
+ - ✨ **TypeScript Support**: Written in TypeScript with full type definitions
13
+
14
+ ## Installation
15
+
16
+ ### Global Installation`
17
+ ```bash
18
+ npm install -g react-native-to-xml-converter
19
+ ```
20
+
21
+ ### Local Installation
22
+ ```bash
23
+ npm install --save-dev react-native-to-xml-converter
24
+ ```
25
+
26
+ ### Using npx (No Installation Required)
27
+ ```bash
28
+ npx react-native-to-xml-converter <input-file>
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ### Basic Usage
34
+ ```bash
35
+ # Using installed package
36
+ rn-to-xml example.ts
37
+
38
+ # Using npx
39
+ npx rn-to-xml example.ts
40
+ ```
41
+
42
+ The converted XML file will be saved in the `xml/` folder in your current directory.
43
+
44
+ ### Example
45
+
46
+ **Input (example.ts):**
47
+ ```tsx
48
+ import { View, Text } from 'react-native';
49
+
50
+ export default function Example() {
51
+ return (
52
+ <View style={{ padding: 20, backgroundColor: '#f0f0f0' }}>
53
+ <Text style={{ fontSize: 18, color: '#333' }}>Hello World</Text>
54
+ <View style={{ marginTop: 10 }}>
55
+ <Text>This is a test component</Text>
56
+ </View>
57
+ </View>
58
+ );
59
+ }
60
+ ```
61
+
62
+ **Output (xml/example.xml):**
63
+ ```xml
64
+ <?xml version="1.0" encoding="utf-8"?>
65
+ <LinearLayout
66
+ xmlns:android="http://schemas.android.com/apk/res/android"
67
+ android:id="@+id/view_1"
68
+ android:layout_width="wrap_content"
69
+ android:layout_height="wrap_content"
70
+ android:orientation="vertical"
71
+ android:background="#f0f0f0"
72
+ android:padding="20dp">
73
+ <TextView
74
+ android:id="@+id/text_2"
75
+ android:layout_width="wrap_content"
76
+ android:layout_height="wrap_content"
77
+ android:text="Hello World"
78
+ android:textColor="#333"
79
+ android:textSize="18sp"/>
80
+ <LinearLayout
81
+ android:id="@+id/view_3"
82
+ android:layout_width="wrap_content"
83
+ android:layout_height="wrap_content"
84
+ android:orientation="vertical"
85
+ android:layout_marginTop="10dp">
86
+ <TextView
87
+ android:id="@+id/text_4"
88
+ android:layout_width="wrap_content"
89
+ android:layout_height="wrap_content"
90
+ android:text="This is a test component"/>
91
+ </LinearLayout>
92
+ </LinearLayout>
93
+ ```
94
+
95
+ ## Supported Components
96
+
97
+ | React Native | Android XML |
98
+ |-------------|-------------|
99
+ | `View` | `LinearLayout` |
100
+ | `Text` | `TextView` |
101
+ | `Image` | `ImageView` |
102
+ | `ScrollView` | `ScrollView` |
103
+ | `TextInput` | `EditText` |
104
+ | `Button` | `Button` |
105
+ | `TouchableOpacity` | `FrameLayout` |
106
+ | `FlatList` | `RecyclerView` |
107
+
108
+ ## Supported Styles
109
+
110
+ ### Layout Properties
111
+ - `padding`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight`
112
+ - `paddingHorizontal`, `paddingVertical`
113
+ - `margin`, `marginTop`, `marginBottom`, `marginLeft`, `marginRight`
114
+ - `marginHorizontal`, `marginVertical`
115
+ - `width`, `height`
116
+
117
+ ### View Properties
118
+ - `backgroundColor`
119
+ - `flexDirection` (determines layout orientation)
120
+ - `alignItems`, `justifyContent` (converted to gravity)
121
+
122
+ ### Text Properties
123
+ - `fontSize`
124
+ - `color`
125
+ - `fontWeight`
126
+ - `textAlign`
127
+
128
+ ## Programmatic API
129
+
130
+ You can also use this package programmatically in your Node.js scripts:
131
+
132
+ ```typescript
133
+ import { convertRNToXML } from 'react-native-to-xml-converter';
134
+
135
+ const code = `
136
+ import { View, Text } from 'react-native';
137
+
138
+ export default function App() {
139
+ return (
140
+ <View style={{ padding: 20 }}>
141
+ <Text>Hello World</Text>
142
+ </View>
143
+ );
144
+ }
145
+ `;
146
+
147
+ const xml = convertRNToXML(code);
148
+ console.log(xml);
149
+ ```
150
+
151
+ ## Development
152
+
153
+ ### Building from Source
154
+
155
+ ```bash
156
+ # Clone the repository
157
+ git clone https://github.com/Adhidevudayakumar/react-native-to-xml-converter.git
158
+ cd react-native-to-xml-converter
159
+
160
+ # Install dependencies
161
+ npm install
162
+
163
+ # Build the project
164
+ npm run build
165
+
166
+ # Link locally for testing
167
+ npm link
168
+
169
+ # Test the CLI
170
+ npx rn-to-xml example.ts
171
+ ```
172
+
173
+ ### Project Structure
174
+
175
+ ```
176
+ ├── src/
177
+ │ ├── cli/ # CLI entry point
178
+ │ ├── parser/ # AST parsing logic
179
+ │ ├── transformer/ # Component and style mappers
180
+ │ ├── generators/ # XML generation
181
+ │ ├── utils/ # Helper utilities
182
+ │ └── index.ts # Public API
183
+ ├── dist/ # Compiled output
184
+ ├── xml/ # Generated XML files (created on first run)
185
+ └── package.json
186
+ ```
187
+
188
+ ## Limitations
189
+
190
+ - Only supports inline styles (not StyleSheet.create references yet)
191
+ - Some complex React Native components may not have direct Android equivalents
192
+ - Nested Text components are flattened to single TextView
193
+ - Custom components are treated as View by default
194
+
195
+ ## Contributing
196
+
197
+ Contributions are welcome! Please feel free to submit a Pull Request.
198
+
199
+ ## License
200
+
201
+ ISC
202
+
203
+ ## Author
204
+
205
+ Adhidev Udayakumar
206
+
207
+ ## Links
208
+
209
+ - [GitHub Repository](https://github.com/Adhidevudayakumar/react-native-to-xml-converter)
210
+ - [Report Issues](https://github.com/Adhidevudayakumar/react-native-to-xml-converter/issues)
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // CLI entry point
4
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
+ if (k2 === undefined) k2 = k;
6
+ var desc = Object.getOwnPropertyDescriptor(m, k);
7
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
+ desc = { enumerable: true, get: function() { return m[k]; } };
9
+ }
10
+ Object.defineProperty(o, k2, desc);
11
+ }) : (function(o, m, k, k2) {
12
+ if (k2 === undefined) k2 = k;
13
+ o[k2] = m[k];
14
+ }));
15
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
17
+ }) : function(o, v) {
18
+ o["default"] = v;
19
+ });
20
+ var __importStar = (this && this.__importStar) || (function () {
21
+ var ownKeys = function(o) {
22
+ ownKeys = Object.getOwnPropertyNames || function (o) {
23
+ var ar = [];
24
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
25
+ return ar;
26
+ };
27
+ return ownKeys(o);
28
+ };
29
+ return function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ })();
37
+ Object.defineProperty(exports, "__esModule", { value: true });
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const index_1 = require("../index");
41
+ /**
42
+ * Main CLI function
43
+ */
44
+ function main() {
45
+ const args = process.argv.slice(2);
46
+ // Check if file path is provided
47
+ if (args.length === 0) {
48
+ console.error('❌ Error: No input file specified');
49
+ console.log('\nUsage: npx rn-to-xml <input-file.ts|tsx>');
50
+ console.log('Example: npx rn-to-xml example.ts');
51
+ process.exit(1);
52
+ }
53
+ const inputFile = args[0];
54
+ const absolutePath = path.resolve(process.cwd(), inputFile);
55
+ // Check if file exists
56
+ if (!fs.existsSync(absolutePath)) {
57
+ console.error(`❌ Error: File not found: ${inputFile}`);
58
+ process.exit(1);
59
+ }
60
+ // Read input file
61
+ let code;
62
+ try {
63
+ code = fs.readFileSync(absolutePath, 'utf-8');
64
+ }
65
+ catch (error) {
66
+ console.error(`❌ Error reading file: ${error.message}`);
67
+ process.exit(1);
68
+ }
69
+ // Convert to XML
70
+ let xml;
71
+ try {
72
+ console.log('🔄 Converting React Native to XML...');
73
+ xml = (0, index_1.convertRNToXML)(code);
74
+ }
75
+ catch (error) {
76
+ console.error(`❌ Conversion failed: ${error.message}`);
77
+ process.exit(1);
78
+ }
79
+ // Create output directory
80
+ const outputDir = path.join(process.cwd(), 'xml');
81
+ if (!fs.existsSync(outputDir)) {
82
+ fs.mkdirSync(outputDir, { recursive: true });
83
+ }
84
+ // Generate output filename
85
+ const inputBaseName = path.basename(inputFile, path.extname(inputFile));
86
+ const outputFile = path.join(outputDir, `${inputBaseName}.xml`);
87
+ // Write output file
88
+ try {
89
+ fs.writeFileSync(outputFile, xml, 'utf-8');
90
+ }
91
+ catch (error) {
92
+ console.error(`❌ Error writing output file: ${error.message}`);
93
+ process.exit(1);
94
+ }
95
+ // Success message
96
+ console.log('✅ Conversion successful!');
97
+ console.log(`📁 Output saved to: ${path.relative(process.cwd(), outputFile)}`);
98
+ }
99
+ // Run CLI
100
+ main();
@@ -0,0 +1,9 @@
1
+ import { XMLNode } from '../parser/astTypes';
2
+ /**
3
+ * Generates XML string from XML tree
4
+ */
5
+ export declare function generateXml(root: XMLNode): string;
6
+ /**
7
+ * Formats generated XML for better readability
8
+ */
9
+ export declare function formatGeneratedXml(xml: string): string;
@@ -0,0 +1,90 @@
1
+ "use strict";
2
+ // Converts tree → XML string
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.generateXml = generateXml;
5
+ exports.formatGeneratedXml = formatGeneratedXml;
6
+ const helpers_1 = require("../utils/helpers");
7
+ /**
8
+ * Generates XML string from XML tree
9
+ */
10
+ function generateXml(root) {
11
+ const xmlDeclaration = '<?xml version="1.0" encoding="utf-8"?>';
12
+ const rootElement = generateElement(root, 0);
13
+ return `${xmlDeclaration}\n${rootElement}`;
14
+ }
15
+ /**
16
+ * Generates XML element recursively
17
+ */
18
+ function generateElement(node, depth) {
19
+ const indent = ' '.repeat(depth);
20
+ const childIndent = ' '.repeat(depth + 1);
21
+ // Prepare attributes
22
+ const attributes = { ...node.attributes };
23
+ if (depth === 0) {
24
+ attributes['xmlns:android'] = 'http://schemas.android.com/apk/res/android';
25
+ }
26
+ // precise attribute order
27
+ const getAttrOrder = (key) => {
28
+ if (key === 'xmlns:android')
29
+ return 0;
30
+ if (key === 'android:id')
31
+ return 1;
32
+ if (key === 'android:layout_width')
33
+ return 2;
34
+ if (key === 'android:layout_height')
35
+ return 3;
36
+ if (key === 'android:orientation')
37
+ return 4;
38
+ return 10;
39
+ };
40
+ // Build attribute string with sorting
41
+ const attrString = Object.entries(attributes)
42
+ .sort((a, b) => {
43
+ const orderA = getAttrOrder(a[0]);
44
+ const orderB = getAttrOrder(b[0]);
45
+ if (orderA !== orderB)
46
+ return orderA - orderB;
47
+ return a[0].localeCompare(b[0]);
48
+ })
49
+ .map(([key, value]) => `${key}="${(0, helpers_1.escapeXml)(value)}"`)
50
+ .join('\n' + indent + ' ');
51
+ // Decide whether to self-close
52
+ const hasChildren = node.children.length > 0;
53
+ // textContent might still be present for some elements if we didn't move it to attribute
54
+ const hasText = !!node.textContent;
55
+ // Opening tag
56
+ let xml = `${indent}<${node.tag}`;
57
+ if (attrString) {
58
+ xml += `\n${indent} ${attrString}`;
59
+ }
60
+ if (!hasChildren && !hasText) {
61
+ xml += '/>';
62
+ return xml;
63
+ }
64
+ xml += '>';
65
+ // Text content
66
+ if (hasText) {
67
+ xml += `\n${childIndent}${(0, helpers_1.escapeXml)(node.textContent)}`;
68
+ }
69
+ // Children
70
+ if (hasChildren) {
71
+ xml += '\n';
72
+ for (const child of node.children) {
73
+ xml += generateElement(child, depth + 1) + '\n';
74
+ }
75
+ xml += indent;
76
+ }
77
+ else if (hasText) {
78
+ xml += '\n' + indent;
79
+ }
80
+ // Closing tag
81
+ xml += `</${node.tag}>`;
82
+ return xml;
83
+ }
84
+ /**
85
+ * Formats generated XML for better readability
86
+ */
87
+ function formatGeneratedXml(xml) {
88
+ // The XML is already formatted by generateElement
89
+ return xml;
90
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Converts React Native code to Android XML layout
3
+ * @param code - React Native component code (JSX/TSX)
4
+ * @returns Android XML layout string
5
+ */
6
+ export declare function convertRNToXML(code: string): string;
7
+ export type { XMLNode, ConvertedStyles, ComponentMapping } from './parser/astTypes';
8
+ export type { ParsedRNTree } from './parser/parseRN';
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ // Main public API entry
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.convertRNToXML = convertRNToXML;
5
+ const parseRN_1 = require("./parser/parseRN");
6
+ const layoutBuilder_1 = require("./transformer/layoutBuilder");
7
+ const xmlGenerator_1 = require("./generators/xmlGenerator");
8
+ const helpers_1 = require("./utils/helpers");
9
+ /**
10
+ * Converts React Native code to Android XML layout
11
+ * @param code - React Native component code (JSX/TSX)
12
+ * @returns Android XML layout string
13
+ */
14
+ function convertRNToXML(code) {
15
+ try {
16
+ // Reset ID counter for each conversion
17
+ (0, helpers_1.resetIdCounter)();
18
+ // Parse React Native code
19
+ const { rootJSXElement } = (0, parseRN_1.parseRN)(code);
20
+ // Build XML tree
21
+ const xmlTree = (0, layoutBuilder_1.buildXmlTree)(rootJSXElement);
22
+ // Generate XML string
23
+ const xml = (0, xmlGenerator_1.generateXml)(xmlTree);
24
+ return xml;
25
+ }
26
+ catch (error) {
27
+ throw new Error(`Conversion failed: ${error.message}`);
28
+ }
29
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Represents an XML node in the Android layout tree
3
+ */
4
+ export interface XMLNode {
5
+ /** Tag name (e.g., 'LinearLayout', 'TextView') */
6
+ tag: string;
7
+ /** XML attributes (e.g., { 'android:layout_width': 'match_parent' }) */
8
+ attributes: Record<string, string>;
9
+ /** Child nodes */
10
+ children: XMLNode[];
11
+ /** Text content (for TextView, etc.) */
12
+ textContent?: string;
13
+ }
14
+ /**
15
+ * Represents converted React Native styles
16
+ */
17
+ export interface ConvertedStyles {
18
+ /** Layout-related attributes */
19
+ layout: Record<string, string>;
20
+ /** View-specific attributes */
21
+ view: Record<string, string>;
22
+ /** Text-specific attributes */
23
+ text: Record<string, string>;
24
+ }
25
+ /**
26
+ * React Native component mapping
27
+ */
28
+ export interface ComponentMapping {
29
+ /** React Native component name */
30
+ rnComponent: string;
31
+ /** Android XML tag */
32
+ xmlTag: string;
33
+ /** Whether it can have children */
34
+ canHaveChildren: boolean;
35
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // AST type definitions
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import * as t from "@babel/types";
2
+ export interface ParsedRNTree {
3
+ ast: t.File;
4
+ rootJSXElement: t.JSXElement;
5
+ }
6
+ export declare function parseRN(code: string): ParsedRNTree;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ // Uses Babel / TS AST to parse RN code
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.parseRN = parseRN;
41
+ const parser_1 = require("@babel/parser");
42
+ const traverse_1 = __importDefault(require("@babel/traverse"));
43
+ const t = __importStar(require("@babel/types"));
44
+ function parseRN(code) {
45
+ try {
46
+ // Parse code into AST
47
+ const ast = (0, parser_1.parse)(code, {
48
+ sourceType: "module",
49
+ plugins: ["jsx", "typescript"],
50
+ });
51
+ let rootJSXElement = null;
52
+ // Traverse AST to find first returned JSX element
53
+ (0, traverse_1.default)(ast, {
54
+ ReturnStatement(path) {
55
+ const arg = path.node.argument;
56
+ if (t.isJSXElement(arg)) {
57
+ rootJSXElement = arg;
58
+ path.stop();
59
+ }
60
+ },
61
+ });
62
+ // If no JSX root found, throw explicit error
63
+ if (!rootJSXElement) {
64
+ throw new Error("No root JSX element found. Make sure your component returns JSX.");
65
+ }
66
+ return { ast, rootJSXElement };
67
+ }
68
+ catch (err) {
69
+ // Re-throw with clean message for CLI / caller
70
+ throw new Error(`RN Parser Error: ${err.message}`);
71
+ }
72
+ }
@@ -0,0 +1,6 @@
1
+ import * as t from '@babel/types';
2
+ import { XMLNode } from '../parser/astTypes';
3
+ /**
4
+ * Builds an XML tree from a JSX element
5
+ */
6
+ export declare function buildXmlTree(jsxElement: t.JSXElement): XMLNode;
@@ -0,0 +1,200 @@
1
+ "use strict";
2
+ // Builds final XML tree
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.buildXmlTree = buildXmlTree;
38
+ const t = __importStar(require("@babel/types"));
39
+ const rnNodeMapper_1 = require("./rnNodeMapper");
40
+ const styleMapper_1 = require("./styleMapper");
41
+ const helpers_1 = require("../utils/helpers");
42
+ /**
43
+ * Builds an XML tree from a JSX element
44
+ */
45
+ function buildXmlTree(jsxElement) {
46
+ const elementName = getElementName(jsxElement.openingElement);
47
+ const xmlTag = (0, rnNodeMapper_1.mapComponentToXml)(elementName);
48
+ // Extract attributes and styles
49
+ const { attributes, styles } = extractAttributes(jsxElement.openingElement);
50
+ // Map styles to Android attributes
51
+ const convertedStyles = (0, styleMapper_1.mapStyles)(styles);
52
+ // Merge all attributes
53
+ const allAttributes = {
54
+ ...attributes,
55
+ ...convertedStyles.layout,
56
+ ...convertedStyles.view,
57
+ ...convertedStyles.text,
58
+ };
59
+ // Add default layout attributes if not present
60
+ if (!allAttributes['android:layout_width']) {
61
+ allAttributes['android:layout_width'] = 'wrap_content';
62
+ }
63
+ if (!allAttributes['android:layout_height']) {
64
+ allAttributes['android:layout_height'] = 'wrap_content';
65
+ }
66
+ // Add orientation for layouts
67
+ if (xmlTag === 'LinearLayout' && !allAttributes['android:orientation']) {
68
+ const orientation = (0, rnNodeMapper_1.getOrientation)(styles?.flexDirection);
69
+ allAttributes['android:orientation'] = orientation;
70
+ }
71
+ // Generate ID
72
+ allAttributes['android:id'] = (0, helpers_1.generateId)(elementName.toLowerCase());
73
+ // Build children and extract text content
74
+ const children = [];
75
+ let textContent;
76
+ // Always process children to extract text content
77
+ for (const child of jsxElement.children) {
78
+ if (t.isJSXElement(child)) {
79
+ // Only add as child if the component can have children
80
+ if ((0, rnNodeMapper_1.canHaveChildren)(elementName)) {
81
+ children.push(buildXmlTree(child));
82
+ }
83
+ }
84
+ else if (t.isJSXText(child)) {
85
+ const text = child.value.trim();
86
+ if (text) {
87
+ textContent = text;
88
+ }
89
+ }
90
+ else if (t.isJSXExpressionContainer(child)) {
91
+ // Handle expressions like {variable}
92
+ if (t.isStringLiteral(child.expression)) {
93
+ textContent = child.expression.value;
94
+ }
95
+ else {
96
+ // For other expressions, use placeholder
97
+ textContent = '{expression}';
98
+ }
99
+ }
100
+ }
101
+ // Move text content to attribute for TextView and Button
102
+ if (textContent && (xmlTag === 'TextView' || xmlTag === 'Button')) {
103
+ allAttributes['android:text'] = textContent;
104
+ textContent = undefined;
105
+ }
106
+ return {
107
+ tag: xmlTag,
108
+ attributes: allAttributes,
109
+ children,
110
+ textContent,
111
+ };
112
+ }
113
+ /**
114
+ * Gets the element name from JSX opening element
115
+ */
116
+ function getElementName(openingElement) {
117
+ const name = openingElement.name;
118
+ if (t.isJSXIdentifier(name)) {
119
+ return name.name;
120
+ }
121
+ return 'View';
122
+ }
123
+ /**
124
+ * Extracts attributes and styles from JSX opening element
125
+ */
126
+ function extractAttributes(openingElement) {
127
+ const attributes = {};
128
+ let styles = {};
129
+ for (const attr of openingElement.attributes) {
130
+ if (t.isJSXAttribute(attr)) {
131
+ const attrName = t.isJSXIdentifier(attr.name) ? attr.name.name : '';
132
+ if (attrName === 'style') {
133
+ styles = extractStyleValue(attr.value);
134
+ }
135
+ else if (attrName === 'testID') {
136
+ // Map testID to contentDescription
137
+ const value = extractAttributeValue(attr.value);
138
+ if (value) {
139
+ attributes['android:contentDescription'] = value;
140
+ }
141
+ }
142
+ else {
143
+ // Other attributes - map directly
144
+ const value = extractAttributeValue(attr.value);
145
+ if (value) {
146
+ attributes[attrName] = value;
147
+ }
148
+ }
149
+ }
150
+ }
151
+ return { attributes, styles };
152
+ }
153
+ /**
154
+ * Extracts the value from JSX attribute value
155
+ */
156
+ function extractAttributeValue(value) {
157
+ if (t.isStringLiteral(value)) {
158
+ return value.value;
159
+ }
160
+ if (t.isJSXExpressionContainer(value)) {
161
+ if (t.isStringLiteral(value.expression)) {
162
+ return value.expression.value;
163
+ }
164
+ if (t.isNumericLiteral(value.expression)) {
165
+ return value.expression.value.toString();
166
+ }
167
+ if (t.isBooleanLiteral(value.expression)) {
168
+ return value.expression.value.toString();
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * Extracts style object from JSX attribute value
175
+ */
176
+ function extractStyleValue(value) {
177
+ if (t.isJSXExpressionContainer(value)) {
178
+ if (t.isObjectExpression(value.expression)) {
179
+ // Parse inline style object
180
+ const styleObj = {};
181
+ for (const prop of value.expression.properties) {
182
+ if (t.isObjectProperty(prop)) {
183
+ const key = t.isIdentifier(prop.key) ? prop.key.name : '';
184
+ let val;
185
+ if (t.isNumericLiteral(prop.value)) {
186
+ val = prop.value.value;
187
+ }
188
+ else if (t.isStringLiteral(prop.value)) {
189
+ val = prop.value.value;
190
+ }
191
+ if (key && val !== undefined) {
192
+ styleObj[key] = val;
193
+ }
194
+ }
195
+ }
196
+ return styleObj;
197
+ }
198
+ }
199
+ return {};
200
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Maps a React Native component name to an Android XML tag
3
+ */
4
+ export declare function mapComponentToXml(componentName: string): string;
5
+ /**
6
+ * Checks if a component can have children
7
+ */
8
+ export declare function canHaveChildren(componentName: string): boolean;
9
+ /**
10
+ * Determines the orientation for a layout based on flexDirection
11
+ */
12
+ export declare function getOrientation(flexDirection?: string): string;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // Maps RN components → XML nodes
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.mapComponentToXml = mapComponentToXml;
5
+ exports.canHaveChildren = canHaveChildren;
6
+ exports.getOrientation = getOrientation;
7
+ /**
8
+ * Component mapping table
9
+ */
10
+ const COMPONENT_MAP = [
11
+ { rnComponent: 'View', xmlTag: 'LinearLayout', canHaveChildren: true },
12
+ { rnComponent: 'Text', xmlTag: 'TextView', canHaveChildren: false },
13
+ { rnComponent: 'Image', xmlTag: 'ImageView', canHaveChildren: false },
14
+ { rnComponent: 'ScrollView', xmlTag: 'ScrollView', canHaveChildren: true },
15
+ { rnComponent: 'FlatList', xmlTag: 'RecyclerView', canHaveChildren: false },
16
+ { rnComponent: 'TextInput', xmlTag: 'EditText', canHaveChildren: false },
17
+ { rnComponent: 'Button', xmlTag: 'Button', canHaveChildren: false },
18
+ { rnComponent: 'TouchableOpacity', xmlTag: 'FrameLayout', canHaveChildren: true },
19
+ { rnComponent: 'TouchableHighlight', xmlTag: 'FrameLayout', canHaveChildren: true },
20
+ { rnComponent: 'Pressable', xmlTag: 'FrameLayout', canHaveChildren: true },
21
+ { rnComponent: 'SafeAreaView', xmlTag: 'LinearLayout', canHaveChildren: true },
22
+ ];
23
+ /**
24
+ * Maps a React Native component name to an Android XML tag
25
+ */
26
+ function mapComponentToXml(componentName) {
27
+ const mapping = COMPONENT_MAP.find(m => m.rnComponent === componentName);
28
+ return mapping?.xmlTag || 'View';
29
+ }
30
+ /**
31
+ * Checks if a component can have children
32
+ */
33
+ function canHaveChildren(componentName) {
34
+ const mapping = COMPONENT_MAP.find(m => m.rnComponent === componentName);
35
+ return mapping?.canHaveChildren ?? true;
36
+ }
37
+ /**
38
+ * Determines the orientation for a layout based on flexDirection
39
+ */
40
+ function getOrientation(flexDirection) {
41
+ if (!flexDirection || flexDirection === 'column') {
42
+ return 'vertical';
43
+ }
44
+ if (flexDirection === 'row') {
45
+ return 'horizontal';
46
+ }
47
+ return 'vertical';
48
+ }
@@ -0,0 +1,9 @@
1
+ import { ConvertedStyles } from '../parser/astTypes';
2
+ /**
3
+ * Maps React Native style properties to Android XML attributes
4
+ */
5
+ export declare function mapStyles(style: any): ConvertedStyles;
6
+ /**
7
+ * Extracts inline styles from JSX element
8
+ */
9
+ export declare function extractInlineStyles(styleValue: any): any;
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ // Converts RN styles → Android attrs
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.mapStyles = mapStyles;
5
+ exports.extractInlineStyles = extractInlineStyles;
6
+ const helpers_1 = require("../utils/helpers");
7
+ /**
8
+ * Maps React Native style properties to Android XML attributes
9
+ */
10
+ function mapStyles(style) {
11
+ const result = {
12
+ layout: {},
13
+ view: {},
14
+ text: {},
15
+ };
16
+ if (!style || typeof style !== 'object') {
17
+ return result;
18
+ }
19
+ // Layout properties
20
+ if (style.padding !== undefined) {
21
+ result.layout['android:padding'] = (0, helpers_1.toDp)(style.padding);
22
+ }
23
+ if (style.paddingTop !== undefined) {
24
+ result.layout['android:paddingTop'] = (0, helpers_1.toDp)(style.paddingTop);
25
+ }
26
+ if (style.paddingBottom !== undefined) {
27
+ result.layout['android:paddingBottom'] = (0, helpers_1.toDp)(style.paddingBottom);
28
+ }
29
+ if (style.paddingLeft !== undefined) {
30
+ result.layout['android:paddingLeft'] = (0, helpers_1.toDp)(style.paddingLeft);
31
+ }
32
+ if (style.paddingRight !== undefined) {
33
+ result.layout['android:paddingRight'] = (0, helpers_1.toDp)(style.paddingRight);
34
+ }
35
+ if (style.paddingHorizontal !== undefined) {
36
+ result.layout['android:paddingLeft'] = (0, helpers_1.toDp)(style.paddingHorizontal);
37
+ result.layout['android:paddingRight'] = (0, helpers_1.toDp)(style.paddingHorizontal);
38
+ }
39
+ if (style.paddingVertical !== undefined) {
40
+ result.layout['android:paddingTop'] = (0, helpers_1.toDp)(style.paddingVertical);
41
+ result.layout['android:paddingBottom'] = (0, helpers_1.toDp)(style.paddingVertical);
42
+ }
43
+ if (style.margin !== undefined) {
44
+ result.layout['android:layout_margin'] = (0, helpers_1.toDp)(style.margin);
45
+ }
46
+ if (style.marginTop !== undefined) {
47
+ result.layout['android:layout_marginTop'] = (0, helpers_1.toDp)(style.marginTop);
48
+ }
49
+ if (style.marginBottom !== undefined) {
50
+ result.layout['android:layout_marginBottom'] = (0, helpers_1.toDp)(style.marginBottom);
51
+ }
52
+ if (style.marginLeft !== undefined) {
53
+ result.layout['android:layout_marginLeft'] = (0, helpers_1.toDp)(style.marginLeft);
54
+ }
55
+ if (style.marginRight !== undefined) {
56
+ result.layout['android:layout_marginRight'] = (0, helpers_1.toDp)(style.marginRight);
57
+ }
58
+ if (style.marginHorizontal !== undefined) {
59
+ result.layout['android:layout_marginLeft'] = (0, helpers_1.toDp)(style.marginHorizontal);
60
+ result.layout['android:layout_marginRight'] = (0, helpers_1.toDp)(style.marginHorizontal);
61
+ }
62
+ if (style.marginVertical !== undefined) {
63
+ result.layout['android:layout_marginTop'] = (0, helpers_1.toDp)(style.marginVertical);
64
+ result.layout['android:layout_marginBottom'] = (0, helpers_1.toDp)(style.marginVertical);
65
+ }
66
+ // Width and Height
67
+ if (style.width !== undefined) {
68
+ if (style.width === '100%') {
69
+ result.layout['android:layout_width'] = 'match_parent';
70
+ }
71
+ else {
72
+ result.layout['android:layout_width'] = (0, helpers_1.toDp)(style.width);
73
+ }
74
+ }
75
+ if (style.height !== undefined) {
76
+ if (style.height === '100%') {
77
+ result.layout['android:layout_height'] = 'match_parent';
78
+ }
79
+ else {
80
+ result.layout['android:layout_height'] = (0, helpers_1.toDp)(style.height);
81
+ }
82
+ }
83
+ // View properties
84
+ if (style.backgroundColor !== undefined) {
85
+ result.view['android:background'] = (0, helpers_1.convertColor)(style.backgroundColor);
86
+ }
87
+ // Text properties
88
+ if (style.fontSize !== undefined) {
89
+ result.text['android:textSize'] = (0, helpers_1.toSp)(style.fontSize);
90
+ }
91
+ if (style.color !== undefined) {
92
+ result.text['android:textColor'] = (0, helpers_1.convertColor)(style.color);
93
+ }
94
+ if (style.fontWeight !== undefined) {
95
+ if (style.fontWeight === 'bold' || parseInt(style.fontWeight) >= 600) {
96
+ result.text['android:textStyle'] = 'bold';
97
+ }
98
+ }
99
+ if (style.textAlign !== undefined) {
100
+ const alignment = {
101
+ 'left': 'left',
102
+ 'center': 'center',
103
+ 'right': 'right',
104
+ 'justify': 'justify',
105
+ };
106
+ result.text['android:textAlignment'] = alignment[style.textAlign] || 'left';
107
+ }
108
+ // Flex properties (basic support)
109
+ if (style.flexDirection !== undefined) {
110
+ // This will be handled at the layout level in layoutBuilder
111
+ }
112
+ if (style.alignItems !== undefined) {
113
+ const gravity = {
114
+ 'flex-start': 'start',
115
+ 'flex-end': 'end',
116
+ 'center': 'center',
117
+ 'stretch': 'fill',
118
+ };
119
+ result.view['android:gravity'] = gravity[style.alignItems] || 'start';
120
+ }
121
+ if (style.justifyContent !== undefined) {
122
+ const gravity = {
123
+ 'flex-start': 'start',
124
+ 'flex-end': 'end',
125
+ 'center': 'center',
126
+ 'space-between': 'space_between',
127
+ 'space-around': 'space_around',
128
+ };
129
+ const currentGravity = result.view['android:gravity'] || '';
130
+ const justifyGravity = gravity[style.justifyContent] || 'start';
131
+ result.view['android:gravity'] = currentGravity
132
+ ? `${currentGravity}|${justifyGravity}`
133
+ : justifyGravity;
134
+ }
135
+ return result;
136
+ }
137
+ /**
138
+ * Extracts inline styles from JSX element
139
+ */
140
+ function extractInlineStyles(styleValue) {
141
+ if (!styleValue) {
142
+ return {};
143
+ }
144
+ // Handle object expression (inline styles)
145
+ if (typeof styleValue === 'object') {
146
+ return styleValue;
147
+ }
148
+ // For now, we'll just return empty object for other cases
149
+ // In a full implementation, you'd resolve StyleSheet.create references
150
+ return {};
151
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Converts camelCase to snake_case
3
+ */
4
+ export declare function camelToSnakeCase(str: string): string;
5
+ /**
6
+ * Converts a numeric value to Android dp units
7
+ */
8
+ export declare function toDp(value: number | string): string;
9
+ /**
10
+ * Converts a numeric value to Android sp units (for text)
11
+ */
12
+ export declare function toSp(value: number | string): string;
13
+ export declare function generateId(prefix?: string): string;
14
+ /**
15
+ * Resets the ID counter (useful for testing)
16
+ */
17
+ export declare function resetIdCounter(): void;
18
+ /**
19
+ * Converts hex color to Android color format
20
+ */
21
+ export declare function convertColor(color: string): string;
22
+ /**
23
+ * Escapes special XML characters in text content
24
+ */
25
+ export declare function escapeXml(text: string): string;
26
+ /**
27
+ * Formats XML with proper indentation
28
+ */
29
+ export declare function formatXml(xml: string, indentLevel?: number): string;
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ // Utility helper functions
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.camelToSnakeCase = camelToSnakeCase;
5
+ exports.toDp = toDp;
6
+ exports.toSp = toSp;
7
+ exports.generateId = generateId;
8
+ exports.resetIdCounter = resetIdCounter;
9
+ exports.convertColor = convertColor;
10
+ exports.escapeXml = escapeXml;
11
+ exports.formatXml = formatXml;
12
+ /**
13
+ * Converts camelCase to snake_case
14
+ */
15
+ function camelToSnakeCase(str) {
16
+ return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
17
+ }
18
+ /**
19
+ * Converts a numeric value to Android dp units
20
+ */
21
+ function toDp(value) {
22
+ if (typeof value === 'string') {
23
+ return value; // Already a string, assume it's formatted
24
+ }
25
+ return `${value}dp`;
26
+ }
27
+ /**
28
+ * Converts a numeric value to Android sp units (for text)
29
+ */
30
+ function toSp(value) {
31
+ if (typeof value === 'string') {
32
+ return value;
33
+ }
34
+ return `${value}sp`;
35
+ }
36
+ /**
37
+ * Generates a unique Android view ID
38
+ */
39
+ let idCounter = 0;
40
+ function generateId(prefix = 'view') {
41
+ return `@+id/${prefix}_${++idCounter}`;
42
+ }
43
+ /**
44
+ * Resets the ID counter (useful for testing)
45
+ */
46
+ function resetIdCounter() {
47
+ idCounter = 0;
48
+ }
49
+ /**
50
+ * Converts hex color to Android color format
51
+ */
52
+ function convertColor(color) {
53
+ // If it's already a hex color, ensure it has # prefix
54
+ if (color.startsWith('#')) {
55
+ return color;
56
+ }
57
+ // Handle named colors (basic support)
58
+ const namedColors = {
59
+ 'black': '#000000',
60
+ 'white': '#FFFFFF',
61
+ 'red': '#FF0000',
62
+ 'green': '#00FF00',
63
+ 'blue': '#0000FF',
64
+ 'transparent': '#00000000',
65
+ };
66
+ return namedColors[color.toLowerCase()] || color;
67
+ }
68
+ /**
69
+ * Escapes special XML characters in text content
70
+ */
71
+ function escapeXml(text) {
72
+ return text
73
+ .replace(/&/g, '&amp;')
74
+ .replace(/</g, '&lt;')
75
+ .replace(/>/g, '&gt;')
76
+ .replace(/"/g, '&quot;')
77
+ .replace(/'/g, '&apos;');
78
+ }
79
+ /**
80
+ * Formats XML with proper indentation
81
+ */
82
+ function formatXml(xml, indentLevel = 0) {
83
+ const indent = ' '.repeat(indentLevel);
84
+ return xml
85
+ .split('\n')
86
+ .map(line => line.trim() ? indent + line : '')
87
+ .join('\n');
88
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "react-native-to-xml-converter",
3
+ "version": "0.0.1",
4
+ "description": "A converter from react native to xml code",
5
+ "keywords": [
6
+ "react-native",
7
+ "xml",
8
+ "converter",
9
+ "android",
10
+ "development",
11
+ "hybrid"
12
+ ],
13
+ "homepage": "https://github.com/Adhidevudayakumar/react-native-to-xml-converter#readme",
14
+ "bugs": {
15
+ "url": "https://github.com/Adhidevudayakumar/react-native-to-xml-converter/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/Adhidevudayakumar/react-native-to-xml-converter.git"
20
+ },
21
+ "license": "ISC",
22
+ "author": "Adhidev Udayakumar",
23
+ "main": "dist/index.js",
24
+ "bin": {
25
+ "rn-to-xml": "dist/cli/index.js"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "dev": "ts-node src/cli/index.ts",
30
+ "test": "echo \"Error: no test specified\" && exit 1",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "devDependencies": {
34
+ "@types/babel__traverse": "^7.28.0",
35
+ "@types/node": "^20.0.0",
36
+ "ts-node": "^10.9.2",
37
+ "tsup": "^8.5.1",
38
+ "typescript": "^5.9.3"
39
+ },
40
+ "dependencies": {
41
+ "@babel/parser": "^7.28.6",
42
+ "@babel/traverse": "^7.28.6",
43
+ "@babel/types": "^7.28.6",
44
+ "fs": "^0.0.1-security",
45
+ "open": "^11.0.0"
46
+ }
47
+ }