cc6ab3740a2a3755a5391d81ff591af1e82f8183856af95621fcd5adc3f6b0599492eb7296a8341ba5de3c1e2e17988003b2915bcbf8359842f90d3aca3985 4.4 KB

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