XmlElement.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. "use strict";
  2. /**
  3. * Copyright (C) 2016-2019 Michael Kourlas
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. var __importDefault = (this && this.__importDefault) || function (mod) {
  18. return (mod && mod.__esModule) ? mod : { "default": mod };
  19. };
  20. Object.defineProperty(exports, "__esModule", { value: true });
  21. var error_1 = require("../error");
  22. var options_1 = require("../options");
  23. var validate_1 = require("../validate");
  24. var XmlAttribute_1 = __importDefault(require("./XmlAttribute"));
  25. var XmlCdata_1 = __importDefault(require("./XmlCdata"));
  26. var XmlCharData_1 = __importDefault(require("./XmlCharData"));
  27. var XmlCharRef_1 = __importDefault(require("./XmlCharRef"));
  28. var XmlComment_1 = __importDefault(require("./XmlComment"));
  29. var XmlEntityRef_1 = __importDefault(require("./XmlEntityRef"));
  30. var XmlProcInst_1 = __importDefault(require("./XmlProcInst"));
  31. /**
  32. * Represents an XML element.
  33. *
  34. * A sample element is structured as follows, where `{name}` is the name
  35. * of the element:
  36. *
  37. * ```xml
  38. * <{name} attname="attvalue">
  39. * <subelem/>
  40. * <?pitarget picontent?>
  41. * text
  42. * </{name}></pre>
  43. * ```
  44. *
  45. * XML elements can have an unlimited number of attributes, CDATA sections,
  46. * character references, comments, elements, entity references, processing
  47. * instructions, and character data.
  48. *
  49. * An element with no content will be represented using an empty element tag:
  50. *
  51. * ```xml
  52. * <{name}/>
  53. * ```
  54. */
  55. var XmlElement = /** @class */ (function () {
  56. function XmlElement(parent, validation, options) {
  57. this._validation = validation;
  58. if (!(0, validate_1.isUndefined)(options.replaceInvalidCharsInName)) {
  59. this._replaceInvalidCharsInName = options.replaceInvalidCharsInName;
  60. }
  61. else {
  62. this._replaceInvalidCharsInName = false;
  63. }
  64. if (!(0, validate_1.isUndefined)(options.useSelfClosingTagIfEmpty)) {
  65. this._useSelfClosingTagIfEmpty = options.useSelfClosingTagIfEmpty;
  66. }
  67. else {
  68. this._useSelfClosingTagIfEmpty = true;
  69. }
  70. this._children = [];
  71. this._attributeNames = [];
  72. this._parent = parent;
  73. this.name = options.name;
  74. }
  75. Object.defineProperty(XmlElement.prototype, "name", {
  76. /**
  77. * Gets the name of this element.
  78. */
  79. get: function () {
  80. return this._name;
  81. },
  82. /**
  83. * Sets the name of this element.
  84. */
  85. set: function (name) {
  86. if (this._replaceInvalidCharsInName) {
  87. name = (0, validate_1.fixName)(name);
  88. if (name.length === 0) {
  89. throw new Error((0, error_1.getContext)(this.up()) + ": element name should"
  90. + " not be empty");
  91. }
  92. }
  93. else if (this._validation && !(0, validate_1.validateName)(name)) {
  94. if (name.length === 0) {
  95. throw new Error((0, error_1.getContext)(this.up()) + ": element name should"
  96. + " not be empty");
  97. }
  98. else {
  99. throw new Error((0, error_1.getContext)(this.up()) + ": element name"
  100. + (" \"" + name + "\" should not contain characters not")
  101. + " allowed in XML names");
  102. }
  103. }
  104. this._name = name;
  105. },
  106. enumerable: false,
  107. configurable: true
  108. });
  109. /**
  110. * Adds an attribute to this element and returns the new attribute.
  111. */
  112. XmlElement.prototype.attribute = function (options) {
  113. if (this._validation
  114. && this._attributeNames.indexOf(options.name) !== -1) {
  115. throw new Error((0, error_1.getContext)(this.up()) + ": element \"" + this.name + "\""
  116. + " already contains an attribute with the"
  117. + (" name \"" + options.name + "\""));
  118. }
  119. var attribute = new XmlAttribute_1.default(this, this._validation, options);
  120. this._children.push(attribute);
  121. this._attributeNames.push(options.name);
  122. return attribute;
  123. };
  124. /**
  125. * Adds a CDATA section to this element and returns the new CDATA section.
  126. */
  127. XmlElement.prototype.cdata = function (options) {
  128. var cdata = new XmlCdata_1.default(this, this._validation, options);
  129. this._children.push(cdata);
  130. return cdata;
  131. };
  132. /**
  133. * Adds character data to this element and returns the new character data.
  134. */
  135. XmlElement.prototype.charData = function (options) {
  136. var charDataNode = new XmlCharData_1.default(this, this._validation, options);
  137. this._children.push(charDataNode);
  138. return charDataNode;
  139. };
  140. /**
  141. * Adds a character reference to this element and returns the new
  142. * character reference.
  143. */
  144. XmlElement.prototype.charRef = function (options) {
  145. var charRef = new XmlCharRef_1.default(this, this._validation, options);
  146. this._children.push(charRef);
  147. return charRef;
  148. };
  149. /**
  150. * Adds a comment to this element and returns the new comment.
  151. */
  152. XmlElement.prototype.comment = function (options) {
  153. var comment = new XmlComment_1.default(this, this._validation, options);
  154. this._children.push(comment);
  155. return comment;
  156. };
  157. /**
  158. * Adds an element to this element and returns the new element.
  159. */
  160. XmlElement.prototype.element = function (options) {
  161. var element = new XmlElement(this, this._validation, options);
  162. this._children.push(element);
  163. return element;
  164. };
  165. /**
  166. * Adds an entity reference to this element and returns the new entity
  167. * reference.
  168. */
  169. XmlElement.prototype.entityRef = function (options) {
  170. var entityRef = new XmlEntityRef_1.default(this, this._validation, options);
  171. this._children.push(entityRef);
  172. return entityRef;
  173. };
  174. /**
  175. * Adds a processing instruction to this element and returns the new
  176. * processing instruction.
  177. */
  178. XmlElement.prototype.procInst = function (options) {
  179. var procInst = new XmlProcInst_1.default(this, this._validation, options);
  180. this._children.push(procInst);
  181. return procInst;
  182. };
  183. /**
  184. * Returns an XML string representation of this element using the specified
  185. * options.
  186. */
  187. XmlElement.prototype.toString = function (options) {
  188. if (options === void 0) { options = {}; }
  189. return this.toStringWithIndent(options, "");
  190. };
  191. /**
  192. * Returns the parent of this element.
  193. */
  194. XmlElement.prototype.up = function () {
  195. return this._parent;
  196. };
  197. /**
  198. * Returns an XML string representation of this element using the specified
  199. * options and initial indent.
  200. */
  201. XmlElement.prototype.toStringWithIndent = function (options, indent) {
  202. var optionsObj = new options_1.StringOptions(options);
  203. var newIndent = indent + optionsObj.indent;
  204. // Element tag start
  205. var str = "<" + this._name;
  206. // Attributes and other nodes
  207. var nodes = [];
  208. for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
  209. var node = _a[_i];
  210. if (node instanceof XmlAttribute_1.default) {
  211. str += " " + node.toString(options);
  212. }
  213. else {
  214. nodes.push(node);
  215. }
  216. }
  217. // Child nodes
  218. if (nodes.length > 0) {
  219. var childStr = "";
  220. for (var i = 0; i < nodes.length; i++) {
  221. var next = nodes[i];
  222. var nextStr = "";
  223. if (next instanceof XmlElement) {
  224. nextStr += next.toStringWithIndent(optionsObj, newIndent);
  225. }
  226. else {
  227. nextStr += next.toString();
  228. }
  229. var prev = i > 0 ? nodes[i - 1] : undefined;
  230. // Skip empty nodes
  231. if (next instanceof XmlCharData_1.default && next.toString() === "") {
  232. continue;
  233. }
  234. // Line break before child nodes unless all nodes, or at least
  235. // the most recent two, are of type XmlCharacterReference,
  236. // XmlEntityReference, or XmlCharData
  237. if (optionsObj.pretty) {
  238. if (!this.allSameLineNodes(nodes)) {
  239. if (!(i > 0 && this.onSameLine(next, prev))) {
  240. nextStr = optionsObj.newline + newIndent + nextStr;
  241. }
  242. }
  243. }
  244. childStr += nextStr;
  245. }
  246. // Line break before end tag unless all nodes are of type
  247. // XmlCharacterReference, XmlEntityReference, or XmlCharData
  248. if (optionsObj.pretty) {
  249. if (!this.allSameLineNodes(nodes)) {
  250. childStr += optionsObj.newline + indent;
  251. }
  252. }
  253. if (childStr.length === 0 && this._useSelfClosingTagIfEmpty) {
  254. // Element empty tag end
  255. str += "/>";
  256. }
  257. else {
  258. // Element start and end tags
  259. str += ">" + childStr + "</" + this._name + ">";
  260. }
  261. }
  262. else if (this._useSelfClosingTagIfEmpty) {
  263. // Element empty tag end
  264. str += "/>";
  265. }
  266. else {
  267. // Element start and end tags
  268. str += "></" + this._name + ">";
  269. }
  270. return str;
  271. };
  272. /**
  273. * Returns true if the specified nodes are all character references,
  274. * entity references, or character data.
  275. */
  276. XmlElement.prototype.allSameLineNodes = function (nodes) {
  277. for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) {
  278. var node = nodes_1[_i];
  279. if (!((node instanceof XmlCharRef_1.default
  280. || node instanceof XmlEntityRef_1.default
  281. || node instanceof XmlCharData_1.default))) {
  282. return false;
  283. }
  284. }
  285. return true;
  286. };
  287. /**
  288. * Returns true if the specified nodes are all character references,
  289. * entity references, or character data.
  290. */
  291. XmlElement.prototype.onSameLine = function (prev, next) {
  292. return (prev instanceof XmlCharRef_1.default
  293. || prev instanceof XmlEntityRef_1.default
  294. || prev instanceof XmlCharData_1.default)
  295. && (!(0, validate_1.isUndefined)(next)
  296. && (next instanceof XmlCharRef_1.default
  297. || next instanceof XmlEntityRef_1.default
  298. || next instanceof XmlCharData_1.default));
  299. };
  300. return XmlElement;
  301. }());
  302. exports.default = XmlElement;