svg2js.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. 'use strict';
  2. var SAX = require('sax'),
  3. JSAPI = require('./jsAPI.js'),
  4. entityDeclaration = /<!ENTITY\s+(\S+)\s+(?:'([^\']+)'|"([^\"]+)")\s*>/g;
  5. var config = {
  6. strict: true,
  7. trim: false,
  8. normalize: true,
  9. lowercase: true,
  10. xmlns: true,
  11. position: true
  12. };
  13. /**
  14. * Convert SVG (XML) string to SVG-as-JS object.
  15. *
  16. * @param {String} data input data
  17. * @param {Function} callback
  18. */
  19. module.exports = function(data, callback) {
  20. var sax = SAX.parser(config.strict, config),
  21. root = new JSAPI({ elem: '#document' }),
  22. current = root,
  23. stack = [root],
  24. textContext = null,
  25. parsingError = false;
  26. function pushToContent(content) {
  27. content = new JSAPI(content, current);
  28. (current.content = current.content || []).push(content);
  29. return content;
  30. }
  31. sax.ondoctype = function(doctype) {
  32. pushToContent({
  33. doctype: doctype
  34. });
  35. var subsetStart = doctype.indexOf('['),
  36. entityMatch;
  37. if (subsetStart >= 0) {
  38. entityDeclaration.lastIndex = subsetStart;
  39. while ((entityMatch = entityDeclaration.exec(data)) != null) {
  40. sax.ENTITIES[entityMatch[1]] = entityMatch[2] || entityMatch[3];
  41. }
  42. }
  43. };
  44. sax.onprocessinginstruction = function(data) {
  45. pushToContent({
  46. processinginstruction: data
  47. });
  48. };
  49. sax.oncomment = function(comment) {
  50. pushToContent({
  51. comment: comment.trim()
  52. });
  53. };
  54. sax.oncdata = function(cdata) {
  55. pushToContent({
  56. cdata: cdata
  57. });
  58. };
  59. sax.onopentag = function(data) {
  60. var elem = {
  61. elem: data.name,
  62. prefix: data.prefix,
  63. local: data.local
  64. };
  65. if (Object.keys(data.attributes).length) {
  66. elem.attrs = {};
  67. for (var name in data.attributes) {
  68. elem.attrs[name] = {
  69. name: name,
  70. value: data.attributes[name].value,
  71. prefix: data.attributes[name].prefix,
  72. local: data.attributes[name].local
  73. };
  74. }
  75. }
  76. elem = pushToContent(elem);
  77. current = elem;
  78. // Save info about <text> tag to prevent trimming of meaningful whitespace
  79. if (data.name == 'text' && !data.prefix) {
  80. textContext = current;
  81. }
  82. stack.push(elem);
  83. };
  84. sax.ontext = function(text) {
  85. if (/\S/.test(text) || textContext) {
  86. if (!textContext)
  87. text = text.trim();
  88. pushToContent({
  89. text: text
  90. });
  91. }
  92. };
  93. sax.onclosetag = function() {
  94. var last = stack.pop();
  95. // Trim text inside <text> tag.
  96. if (last == textContext) {
  97. trim(textContext);
  98. textContext = null;
  99. }
  100. current = stack[stack.length - 1];
  101. };
  102. sax.onerror = function(e) {
  103. e.message = 'Error in parsing SVG: ' + e.message;
  104. if (e.message.indexOf('Unexpected end') < 0) {
  105. throw e;
  106. }
  107. };
  108. sax.onend = function() {
  109. if (!this.error) {
  110. callback(root);
  111. } else {
  112. callback({ error: this.error.message });
  113. }
  114. };
  115. try {
  116. sax.write(data);
  117. } catch (e) {
  118. callback({ error: e.message });
  119. parsingError = true;
  120. }
  121. if (!parsingError) sax.close();
  122. function trim(elem) {
  123. if (!elem.content) return elem;
  124. var start = elem.content[0],
  125. end = elem.content[elem.content.length - 1];
  126. while (start && start.content && !start.text) start = start.content[0];
  127. if (start && start.text) start.text = start.text.replace(/^\s+/, '');
  128. while (end && end.content && !end.text) end = end.content[end.content.length - 1];
  129. if (end && end.text) end.text = end.text.replace(/\s+$/, '');
  130. return elem;
  131. }
  132. };