js2svg.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. 'use strict';
  2. var EOL = require('os').EOL,
  3. textElem = require('../../plugins/_collections.js').elemsGroups.textContent.concat('title');
  4. var defaults = {
  5. doctypeStart: '<!DOCTYPE',
  6. doctypeEnd: '>',
  7. procInstStart: '<?',
  8. procInstEnd: '?>',
  9. tagOpenStart: '<',
  10. tagOpenEnd: '>',
  11. tagCloseStart: '</',
  12. tagCloseEnd: '>',
  13. tagShortStart: '<',
  14. tagShortEnd: '/>',
  15. attrStart: '="',
  16. attrEnd: '"',
  17. commentStart: '<!--',
  18. commentEnd: '-->',
  19. cdataStart: '<![CDATA[',
  20. cdataEnd: ']]>',
  21. textStart: '',
  22. textEnd: '',
  23. indent: 4,
  24. regEntities: /[&'"<>]/g,
  25. regValEntities: /[&"<>]/g,
  26. encodeEntity: encodeEntity,
  27. pretty: false,
  28. useShortTags: true
  29. };
  30. var entities = {
  31. '&': '&amp;',
  32. '\'': '&apos;',
  33. '"': '&quot;',
  34. '>': '&gt;',
  35. '<': '&lt;',
  36. };
  37. /**
  38. * Convert SVG-as-JS object to SVG (XML) string.
  39. *
  40. * @param {Object} data input data
  41. * @param {Object} config config
  42. *
  43. * @return {Object} output data
  44. */
  45. module.exports = function(data, config) {
  46. return new JS2SVG(config).convert(data);
  47. };
  48. function JS2SVG(config) {
  49. if (config) {
  50. this.config = Object.assign({}, defaults, config);
  51. } else {
  52. this.config = Object.assign({}, defaults);
  53. }
  54. var indent = this.config.indent;
  55. if (typeof indent == 'number' && !isNaN(indent)) {
  56. this.config.indent = (indent < 0) ? '\t' : ' '.repeat(indent);
  57. } else if (typeof indent != 'string') {
  58. this.config.indent = ' ';
  59. }
  60. if (this.config.pretty) {
  61. this.config.doctypeEnd += EOL;
  62. this.config.procInstEnd += EOL;
  63. this.config.commentEnd += EOL;
  64. this.config.cdataEnd += EOL;
  65. this.config.tagShortEnd += EOL;
  66. this.config.tagOpenEnd += EOL;
  67. this.config.tagCloseEnd += EOL;
  68. this.config.textEnd += EOL;
  69. }
  70. this.indentLevel = 0;
  71. this.textContext = null;
  72. }
  73. function encodeEntity(char) {
  74. return entities[char];
  75. }
  76. /**
  77. * Start conversion.
  78. *
  79. * @param {Object} data input data
  80. *
  81. * @return {String}
  82. */
  83. JS2SVG.prototype.convert = function(data) {
  84. var svg = '';
  85. if (data.content) {
  86. this.indentLevel++;
  87. data.content.forEach(function(item) {
  88. if (item.elem) {
  89. svg += this.createElem(item);
  90. } else if (item.text) {
  91. svg += this.createText(item.text);
  92. } else if (item.doctype) {
  93. svg += this.createDoctype(item.doctype);
  94. } else if (item.processinginstruction) {
  95. svg += this.createProcInst(item.processinginstruction);
  96. } else if (item.comment) {
  97. svg += this.createComment(item.comment);
  98. } else if (item.cdata) {
  99. svg += this.createCDATA(item.cdata);
  100. }
  101. }, this);
  102. }
  103. this.indentLevel--;
  104. return {
  105. data: svg,
  106. info: {
  107. width: this.width,
  108. height: this.height
  109. }
  110. };
  111. };
  112. /**
  113. * Create indent string in accordance with the current node level.
  114. *
  115. * @return {String}
  116. */
  117. JS2SVG.prototype.createIndent = function() {
  118. var indent = '';
  119. if (this.config.pretty && !this.textContext) {
  120. indent = this.config.indent.repeat(this.indentLevel - 1);
  121. }
  122. return indent;
  123. };
  124. /**
  125. * Create doctype tag.
  126. *
  127. * @param {String} doctype doctype body string
  128. *
  129. * @return {String}
  130. */
  131. JS2SVG.prototype.createDoctype = function(doctype) {
  132. return this.config.doctypeStart +
  133. doctype +
  134. this.config.doctypeEnd;
  135. };
  136. /**
  137. * Create XML Processing Instruction tag.
  138. *
  139. * @param {Object} instruction instruction object
  140. *
  141. * @return {String}
  142. */
  143. JS2SVG.prototype.createProcInst = function(instruction) {
  144. return this.config.procInstStart +
  145. instruction.name +
  146. ' ' +
  147. instruction.body +
  148. this.config.procInstEnd;
  149. };
  150. /**
  151. * Create comment tag.
  152. *
  153. * @param {String} comment comment body
  154. *
  155. * @return {String}
  156. */
  157. JS2SVG.prototype.createComment = function(comment) {
  158. return this.config.commentStart +
  159. comment +
  160. this.config.commentEnd;
  161. };
  162. /**
  163. * Create CDATA section.
  164. *
  165. * @param {String} cdata CDATA body
  166. *
  167. * @return {String}
  168. */
  169. JS2SVG.prototype.createCDATA = function(cdata) {
  170. return this.createIndent() +
  171. this.config.cdataStart +
  172. cdata +
  173. this.config.cdataEnd;
  174. };
  175. /**
  176. * Create element tag.
  177. *
  178. * @param {Object} data element object
  179. *
  180. * @return {String}
  181. */
  182. JS2SVG.prototype.createElem = function(data) {
  183. // beautiful injection for obtaining SVG information :)
  184. if (
  185. data.isElem('svg') &&
  186. data.hasAttr('width') &&
  187. data.hasAttr('height')
  188. ) {
  189. this.width = data.attr('width').value;
  190. this.height = data.attr('height').value;
  191. }
  192. // empty element and short tag
  193. if (data.isEmpty()) {
  194. if (this.config.useShortTags) {
  195. return this.createIndent() +
  196. this.config.tagShortStart +
  197. data.elem +
  198. this.createAttrs(data) +
  199. this.config.tagShortEnd;
  200. } else {
  201. return this.createIndent() +
  202. this.config.tagShortStart +
  203. data.elem +
  204. this.createAttrs(data) +
  205. this.config.tagOpenEnd +
  206. this.config.tagCloseStart +
  207. data.elem +
  208. this.config.tagCloseEnd;
  209. }
  210. // non-empty element
  211. } else {
  212. var tagOpenStart = this.config.tagOpenStart,
  213. tagOpenEnd = this.config.tagOpenEnd,
  214. tagCloseStart = this.config.tagCloseStart,
  215. tagCloseEnd = this.config.tagCloseEnd,
  216. openIndent = this.createIndent(),
  217. textIndent = '',
  218. processedData = '',
  219. dataEnd = '';
  220. if (this.textContext) {
  221. tagOpenStart = defaults.tagOpenStart;
  222. tagOpenEnd = defaults.tagOpenEnd;
  223. tagCloseStart = defaults.tagCloseStart;
  224. tagCloseEnd = defaults.tagCloseEnd;
  225. openIndent = '';
  226. } else if (data.isElem(textElem)) {
  227. if (this.config.pretty) {
  228. textIndent += openIndent + this.config.indent;
  229. }
  230. this.textContext = data;
  231. }
  232. processedData += this.convert(data).data;
  233. if (this.textContext == data) {
  234. this.textContext = null;
  235. if (this.config.pretty) dataEnd = EOL;
  236. }
  237. return openIndent +
  238. tagOpenStart +
  239. data.elem +
  240. this.createAttrs(data) +
  241. tagOpenEnd +
  242. textIndent +
  243. processedData +
  244. dataEnd +
  245. this.createIndent() +
  246. tagCloseStart +
  247. data.elem +
  248. tagCloseEnd;
  249. }
  250. };
  251. /**
  252. * Create element attributes.
  253. *
  254. * @param {Object} elem attributes object
  255. *
  256. * @return {String}
  257. */
  258. JS2SVG.prototype.createAttrs = function(elem) {
  259. var attrs = '';
  260. elem.eachAttr(function(attr) {
  261. if (attr.value !== undefined) {
  262. attrs += ' ' +
  263. attr.name +
  264. this.config.attrStart +
  265. String(attr.value).replace(this.config.regValEntities, this.config.encodeEntity) +
  266. this.config.attrEnd;
  267. }
  268. else {
  269. attrs += ' ' +
  270. attr.name;
  271. }
  272. }, this);
  273. return attrs;
  274. };
  275. /**
  276. * Create text node.
  277. *
  278. * @param {String} text text
  279. *
  280. * @return {String}
  281. */
  282. JS2SVG.prototype.createText = function(text) {
  283. return this.createIndent() +
  284. this.config.textStart +
  285. text.replace(this.config.regEntities, this.config.encodeEntity) +
  286. (this.textContext ? '' : this.config.textEnd);
  287. };