js2svg.js 7.9 KB

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