1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//==================================================================================================
18//
19// Module Name: Xml2WBXml
20//
21// General Description: Convert XML file to WBXML
22//
23//==================================================================================================
24
25package com.mot.dm.core;
26
27import java.util.*;
28import java.io.*;
29import javax.xml.parsers.*;
30import org.w3c.dom.*;
31import org.xml.sax.InputSource;
32
33public class Xml2WBXml {
34  HashMap accessMap = null;
35  HashMap formatMap = null;
36  ArrayList arrResult = new ArrayList();
37  String gformat = null;
38
39  public Xml2WBXml() {
40    initAccessMap();
41    initFormatMap();
42    resetArrResult();
43
44  }
45
46  private void resetArrResult() {
47    //Emit WBXML header with version number, doc ID, charset, and string table.
48    //['\x01\x01\x6A\x00']
49    arrResult.clear();
50    arrResult.add(new Character( (char) 0x01));
51    arrResult.add(new Character( (char) 0x01));
52    arrResult.add(new Character( (char) 0x6A));
53    arrResult.add(new Character( (char) 0x00));
54  }
55
56  //Access Types.
57  //TODO: Should add "Local" later...
58  private void initAccessMap() {
59    accessMap = new HashMap();
60    accessMap.put("Add", new Integer(0x01));
61    accessMap.put("Delete", new Integer(0x02));
62    accessMap.put("Exec", new Integer(0x04));
63    accessMap.put("Get", new Integer(0x08));
64    accessMap.put("Replace", new Integer(0x10));
65  }
66
67  private void initFormatMap() {
68    formatMap = new HashMap();
69    formatMap.put("bin", new Character( (char) 0));
70    formatMap.put("bool", new Character( (char) 1));
71    formatMap.put("b64", new Character( (char) 2));
72    formatMap.put("chr", new Character( (char) 3));
73    formatMap.put("int", new Character( (char) 4));
74    formatMap.put("node", new Character( (char) 5));
75    formatMap.put("null", new Character( (char) 6));
76    formatMap.put("xml", new Character( (char) 7));
77    formatMap.put("test", new Character( (char) 9));
78    formatMap.put("float", new Character( (char) 10));
79    formatMap.put("date", new Character( (char) 11));
80    formatMap.put("time", new Character( (char) 12));
81  }
82
83  private Document getDocument(String filename) throws Exception {
84    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
85    factory.setValidating(false);
86
87    // The following if way around (ugly but working) to support chinese chars for DOM parser:
88    //   - read  file to string
89    //   - get UTF8 byte array
90    //   - send this array to xmp parser.
91
92    FileInputStream fis = new FileInputStream(filename);
93    int x = fis.available();
94    byte raw_bytes[] = new byte[x];
95    fis.read(raw_bytes);
96    String content = new String(raw_bytes);
97    raw_bytes = null;
98    byte[] utf8_bytes = content.getBytes("UTF8");
99
100    return factory.newDocumentBuilder().parse(new InputSource(new
101        ByteArrayInputStream(utf8_bytes)));
102  }
103
104  public void convert(File file) throws Exception {
105    resetArrResult();
106
107    ArrayList arrNodes = new ArrayList();
108    Document document = getDocument(file.getCanonicalPath());
109    NodeList list;
110    list = document.getDocumentElement().getChildNodes();
111
112    for (int i = 0; i < list.getLength(); i++) {
113      Node node = list.item(i);
114      if (node.getNodeType() == Node.ELEMENT_NODE &&
115          node.getNodeName().equals("Node")) {
116        arrNodes.add(node);
117      }
118    }
119
120    if (arrNodes.size() > 0) {
121      this.arrResult.add(new Character( (char) 0x5E));
122
123      for (int i = 0; i < arrNodes.size(); i++) {
124        Node node = (Node) arrNodes.get(i);
125        arrResult.addAll(processNode(node));
126      }
127      this.arrResult.add(new Character( (char) 0x01));
128    }
129    else {
130      this.arrResult.add(new Character( (char) 0x1E));
131    }
132
133    // write to file wbxml from arrResult
134    String wbxmlPath = file.getCanonicalPath();
135    wbxmlPath = (wbxmlPath.substring(0, wbxmlPath.length() - 3)).concat(
136        "wbxml");
137    byte[] arrByte = new byte[arrResult.size()];
138    //convert chars to bytes
139    for (int i = 0; i < arrResult.size(); i++) {
140      char c = ( (Character) arrResult.get(i)).charValue();
141      arrByte[i] = (byte) c;
142    }
143    // write
144    FileOutputStream binaryMetaDataOut = new FileOutputStream(wbxmlPath);
145    DataOutputStream bmdfOut = new DataOutputStream(binaryMetaDataOut);
146    bmdfOut.write(arrByte);
147    bmdfOut.close();
148  }
149
150  private ArrayList processNode(Node node) throws Exception {
151    ArrayList arrCurrResult = new ArrayList();
152    NodeList list = node.getChildNodes();
153    ArrayList arrNodes = new ArrayList();
154
155    for (int i = 0; i < list.getLength(); i++) {
156      Node n = list.item(i);
157      if (n.getNodeType() == Node.ELEMENT_NODE) { ///@@@ add node validation here NodeChildren
158        arrNodes.add(n);
159      }
160    }
161
162    if (arrNodes.size() > 0) {
163      arrCurrResult.add(new Character( (char) 0x5D));
164
165      for (int i = 0; i < arrNodes.size(); i++) {
166        Node n = (Node) arrNodes.get(i);
167
168        //  result.append(NodeChildren[child.tagName](child))
169        if (n.getNodeName().equals("Node")) {
170          arrCurrResult.addAll(processNode(n));
171        }
172        else if (n.getNodeName().equals("Type")) {
173          arrCurrResult.addAll(processNodeType(n));
174        }
175        else if (n.getNodeName().equals("Data")) {
176          arrCurrResult.addAll(processNodeData(n));
177        }
178        else if (n.getNodeName().equals("Plural")) {
179          arrCurrResult.addAll(processNodePlural(n));
180        }
181        else if (n.getNodeName().equals("ClassID")) {
182          arrCurrResult.addAll(processClassID(n));
183        }
184        else if (n.getNodeName().equals("NodeName")) {
185          arrCurrResult.addAll(processNodeName(n));
186        }
187        else if (n.getNodeName().equals("Path")) {
188          arrCurrResult.addAll(processNodePath(n));
189        }
190        else if (n.getNodeName().equals("RTProperties")) {
191          arrCurrResult.addAll(processRTProperties(n));
192        }
193        else if (n.getNodeName().equals("DFProperties")) {
194          arrCurrResult.addAll(processDFProperties(n));
195        }
196        else {
197          throw new Exception("Unsupported node: " + n.getNodeName());
198        }
199      }
200      arrCurrResult.add(new Character( (char) 0x01));
201    }
202    else {
203      arrCurrResult.add(new Character( (char) 0x1D));
204    }
205    return arrCurrResult;
206  }
207
208  private ArrayList processNodeType(Node node) throws Exception {
209    ArrayList arrCurrResult = new ArrayList();
210    String text = getText(node);
211    arrCurrResult.add(new Character( (char) 0x56));
212    arrCurrResult.addAll(opaqueStringData(text));
213    arrCurrResult.add(new Character( (char) 0x01));
214    return arrCurrResult;
215  }
216
217  private ArrayList processNodeData(Node node) throws Exception {
218    ArrayList arrCurrResult = new ArrayList();
219    String nodeData = getText(node);
220    arrCurrResult.add(new Character( (char) 0x48));
221    if ("bin".equals(gformat)) {
222      arrCurrResult.addAll(opaqueHexData(nodeData));
223    }
224    else {
225      arrCurrResult.addAll(opaqueStringData(nodeData));
226    }
227    arrCurrResult.add(new Character( (char) 0x01));
228    return arrCurrResult;
229  }
230
231  private ArrayList processNodePlural(Node node) throws Exception {
232    ArrayList arrCurrResult = new ArrayList();
233    Node nodeYesNo = null;
234    NodeList list = node.getChildNodes();
235
236    for (int i = 0; i < list.getLength(); i++) {
237      Node n = list.item(i);
238      if (n.getNodeType() == Node.ELEMENT_NODE &&
239          (n.getNodeName().equals("Yes") || n.getNodeName().equals("No"))) {
240        nodeYesNo = n;
241        break;
242      }
243    }
244
245    if (nodeYesNo == null) {
246      throw new Exception("Plural element missing Yes or No tag");
247    }
248    else {
249      char yesOrNo = (nodeYesNo.getNodeName().equals("Yes")) ? (char) 1 :
250          (char) 0;
251      arrCurrResult.add(new Character( (char) 0x49));
252      arrCurrResult.addAll(opaqueCharData(yesOrNo));
253      arrCurrResult.add(new Character( (char) 0x01));
254    }
255    return arrCurrResult;
256  }
257
258  private ArrayList processClassID(Node node) throws Exception {
259    ArrayList arrCurrResult = new ArrayList();
260    String strClassId = getText(node);
261    int intClassId = Integer.parseInt(strClassId);
262    arrCurrResult.add(new Character( (char) (0x18 | 0x40)));
263    arrCurrResult.addAll(opaquePackedData(intClassId));
264    arrCurrResult.add(new Character( (char) 0x01));
265    return arrCurrResult;
266  }
267
268  private ArrayList processNodeName(Node node) throws Exception {
269    //text = getText(element)
270    //element.parentNode.SyncMLDM_URI += '/' + text
271    return processTextElement(node, 0x1C);
272  }
273
274  private ArrayList processNodePath(Node node) throws Exception {
275    return processTextElement(node, 0x12);
276  }
277
278  private ArrayList processRTProperties(Node node) throws Exception {
279    throw new Exception("Support for RTProperties not implemented !!!");
280  }
281
282  private ArrayList processDFProperties(Node node) throws Exception {
283    ArrayList arrCurrResult = new ArrayList();
284
285    NodeList list = node.getChildNodes();
286    for (int i = 0; i < list.getLength(); i++) {
287      Node n = list.item(i);
288      if (n.getNodeType() == Node.ELEMENT_NODE) {
289        if (n.getNodeName().equals("AccessType")) {
290          arrCurrResult.addAll(processAccessType(n));
291        }
292        else if (n.getNodeName().equals("DFFormat")) {
293          arrCurrResult.addAll(processDFFormat(n));
294        }
295        else if (n.getNodeName().equals("Scope")) {
296          arrCurrResult.addAll(processScope(n));
297        }
298        else {
299          throw new Exception("The tag " + n.getNodeName() +
300                              " not supprted for DFProperties !!!");
301        }
302      }
303    }
304    return arrCurrResult;
305  }
306
307  private ArrayList processAccessType(Node node) throws Exception {
308    ArrayList arrCurrResult = new ArrayList();
309    NodeList list = node.getChildNodes();
310    int access = 0;
311    int nodeHasGetAccess = 0;
312    Integer integer;
313    for (int i = 0; i < list.getLength(); i++) {
314      Node n = list.item(i);
315      if (n.getNodeType() == Node.ELEMENT_NODE) {
316        integer = (Integer) accessMap.get(n.getNodeName());
317        if (integer != null) {
318          access |= integer.intValue();
319          if ("Get".equalsIgnoreCase(n.getNodeName())) {
320            nodeHasGetAccess = 1;
321          }
322        }
323      }
324    }
325    arrCurrResult.add(new Character( (char) 0x5A));
326    arrCurrResult.addAll(opaquePackedData(access));
327    arrCurrResult.add(new Character( (char) 0x01));
328    if (nodeHasGetAccess == 0) {
329      //result = result + '\x55'+ opaqueData(chr(180))  + '\x01'
330      arrCurrResult.add(new Character( (char) 0x55));
331      arrCurrResult.addAll(opaqueCharData( (char) 180));
332      arrCurrResult.add(new Character( (char) 0x01));
333    }
334    return arrCurrResult;
335  }
336
337  private ArrayList processDFFormat(Node node) throws Exception {
338    ArrayList arrCurrResult = new ArrayList();
339    Character format = null;
340    NodeList list = node.getChildNodes();
341
342    for (int i = 0; i < list.getLength(); i++) {
343      Node n = list.item(i);
344      if (n.getNodeType() == Node.ELEMENT_NODE) {
345        format = (Character) formatMap.get(n.getNodeName());
346        if (format != null) {
347          gformat = n.getNodeName();
348          //WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW
349          //System.out.println("format: " + gformat + "  value: " + format.charValue() + " opaq: " + opaqueCharData(format.charValue()));
350
351          //@@@@@ py ??? gformat = format
352          //@@@@@ py ??? element.parentNode.parentNode.SyncMLDM_Format = format;
353          break;
354        }
355      }
356    }
357
358    if (format == null) {
359      throw new Exception("DFFormat is an unknown or missing format tag");
360    }
361    else {
362      arrCurrResult.add(new Character( (char) 0x57));
363      arrCurrResult.addAll(opaqueCharData(format.charValue()));
364      arrCurrResult.add(new Character( (char) 0x01));
365    }
366    return arrCurrResult;
367  }
368
369  private ArrayList processScope(Node node) throws Exception {
370    ArrayList arrCurrResult = new ArrayList();
371    Node scope = null;
372    NodeList list = node.getChildNodes();
373
374    for (int i = 0; i < list.getLength(); i++) {
375      Node n = list.item(i);
376      if (n.getNodeType() == Node.ELEMENT_NODE &&
377          (n.getNodeName().equals("Permanent") ||
378           n.getNodeName().equals("Dynamic"))) {
379        scope = n;
380        break;
381      }
382    }
383
384    if (scope == null) {
385      throw new Exception("Scope element missing Permanent or Dynamic tag");
386    }
387    else {
388      char charScope = (scope.getNodeName().equals("Permanent")) ? (char) 1 :
389          (char) 2;
390      arrCurrResult.add(new Character( (char) 0x59));
391      arrCurrResult.addAll(opaqueCharData(charScope));
392      arrCurrResult.add(new Character( (char) 0x01));
393    }
394    return arrCurrResult;
395  }
396
397  private String getText(Node node) throws Exception {
398    NodeList list = node.getChildNodes();
399    for (int i = 0; i < list.getLength(); i++) {
400      Node n = list.item(i);
401      if (n.getNodeType() == Node.TEXT_NODE) {
402        return n.getNodeValue();
403      }
404    }
405    return "";
406  }
407
408  public ArrayList opaqueStringData(String data) throws Exception {
409      if(data.startsWith(FactBootEnc.GUID_HEX_BOOTSTRAP)){
410          return opaqueHexData(data.replaceFirst(FactBootEnc.GUID_HEX_BOOTSTRAP, ""));
411      }
412    byte[] utf8_bytes = data.getBytes("UTF8");
413    ArrayList arrOpaqueDate = new ArrayList();
414    arrOpaqueDate.add(new Character( (char) 0xC3));
415    arrOpaqueDate.addAll(multiByte(utf8_bytes.length));
416    for (int i = 0; i < utf8_bytes.length; i++) {
417      arrOpaqueDate.add(new Character(  (char) (((int)utf8_bytes[i]) & 0xFF)  ) );
418    }
419    return arrOpaqueDate;
420
421   /* ArrayList arrOpaqueDate = new ArrayList();
422    arrOpaqueDate.add(new Character( (char) 0xC3));
423    arrOpaqueDate.addAll(multiByte(data.length()));
424    char[] arrChar = data.toCharArray();
425    for (int i = 0; i < arrChar.length; i++) {
426      arrOpaqueDate.add(new Character(arrChar[i]));
427    }
428    return arrOpaqueDate;*/
429  }
430
431  public ArrayList opaqueCharData(char data) throws Exception {
432    ArrayList arrOpaqueDate = new ArrayList();
433    arrOpaqueDate.add(new Character( (char) 0xC3));
434    arrOpaqueDate.addAll(multiByte(1));
435    arrOpaqueDate.add(new Character(data));
436    return arrOpaqueDate;
437  }
438
439  public ArrayList opaquePackedData(int data) throws Exception {
440    ArrayList arrOpaqueDate = new ArrayList();
441    arrOpaqueDate.add(new Character( (char) 0xC3));
442    arrOpaqueDate.addAll(multiByte(2));
443    arrOpaqueDate.add(new Character( (char) (data / 256)));
444    arrOpaqueDate.add(new Character( (char) (data % 256)));
445    return arrOpaqueDate;
446  }
447
448  public ArrayList opaqueHexData(String data) throws Exception {
449    if (data.length() % 2 != 0) {
450      throw new Exception(
451          "HEX-encoded data has incorrect format: data length is not even number.");
452    }
453    ArrayList arrOpaqueDate = new ArrayList();
454    arrOpaqueDate.add(new Character( (char) 0xC3));
455    arrOpaqueDate.addAll(multiByte(data.length() / 2));
456    arrOpaqueDate.addAll(hexToBin(data));
457    return arrOpaqueDate;
458  }
459
460  public ArrayList hexToBin(String data) {
461    ArrayList arrBinDate = new ArrayList();
462    int tmp;
463    for (int i = 0; i < data.length(); i += 2) {
464      tmp = Integer.decode("0x" + data.substring(i, i + 2)).intValue();
465      arrBinDate.add(new Character( (char) tmp));
466    }
467    return arrBinDate;
468  }
469
470  public ArrayList multiByte(int value) throws Exception {
471    ArrayList result = new ArrayList();
472    int continuation = 0;
473    int bits = 0;
474
475    for (int shift = 28; shift > 0; shift -= 7) {
476      bits = (value >> shift) & 0x7F;
477      if (bits > 0 || continuation > 0) {
478        result.add(new Character( (char) (bits | 0x80)));
479      }
480      if (bits > 0) {
481        continuation = 1;
482      }
483    }
484    result.add(new Character( (char) (value & 0x7F)));
485    return result;
486  }
487
488  //Return the WBXML representation of a element with text content
489  // Takes a node and the value of the WBXML tag without content.
490  public ArrayList processTextElement(Node node, int wbxmlTag) throws Exception {
491    ArrayList arrText = new ArrayList();
492    String text = getText(node);
493    if (text.length() > 0) {
494      arrText.add(new Character( (char) (wbxmlTag | 0x40)));
495      arrText.addAll(opaqueStringData(text));
496      arrText.add(new Character( (char) 0x01));
497    }
498    else {
499      arrText.add(new Character( (char) wbxmlTag));
500    }
501    return arrText;
502  }
503
504  public static void main(String[] args) {
505    try {
506      Xml2WBXml x2b = new Xml2WBXml();
507      File file = new File(args[0]);
508      x2b.convert(file);
509    }
510    catch (Exception e) {
511      e.printStackTrace();
512    }
513  }
514}
515