reader.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. 'use strict';
  2. var fs = require('fs');
  3. var $$ = require('./common');
  4. var _errors = require('./errors');
  5. // "\x20-\x7E" -> " -~" for JSLint
  6. var NON_PRINTABLE = new RegExp('[^\x09\x0A\x0D -~\x85\xA0-\uD7FF\uE000-\uFFFD]');
  7. // IE 7-8 hack. As we use ONLY strings in browsers as input stream, there's no
  8. // need for stream.slice() call and we can simply use stream.charAt() when we
  9. // are running on that shit...
  10. var getSingleChar = (undefined === ('a')[0])
  11. ? function (str, pos) { return str.charAt(pos); }
  12. : function (str, pos) { return str[pos]; };
  13. function ReaderError(name, position, character, encoding, reason) {
  14. _errors.YAMLError.apply(this);
  15. this.name = 'ReaderError';
  16. this.name = name;
  17. this.position = position;
  18. this.character = character;
  19. this.encoding = encoding;
  20. this.reason = reason;
  21. this.toString = function toString() {
  22. return 'unacceptable character ' + this.character + ': ' + this.reason +
  23. '\n in "' + this.name + '", position ' + this.position;
  24. };
  25. }
  26. $$.inherits(ReaderError, _errors.YAMLError);
  27. function Reader(stream) {
  28. this.name = '<unicode string>';
  29. this.stream = null;
  30. this.streamPointer = 0;
  31. this.eof = true;
  32. this.buffer = '';
  33. this.pointer = 0;
  34. this.rawBuffer = null;
  35. this.encoding = 'utf-8';
  36. this.index = 0;
  37. this.line = 0;
  38. this.column = 0;
  39. if ('string' === typeof stream) { // simple string
  40. this.name = '<unicode string>';
  41. this.checkPrintable(stream);
  42. this.buffer = stream + '\x00';
  43. } else if (Buffer.isBuffer(stream)) { // buffer
  44. this.name = '<buffer>';
  45. this.rawBuffer = stream;
  46. this.update(1);
  47. } else { // file descriptor
  48. this.name = '<file>';
  49. this.stream = stream;
  50. this.eof = false;
  51. this.updateRaw();
  52. this.update(1);
  53. }
  54. }
  55. Reader.prototype.peek = function peek(index) {
  56. var data;
  57. index = +index || 0;
  58. data = getSingleChar(this.buffer, this.pointer + index);
  59. if (undefined === data) {
  60. this.update(index + 1);
  61. data = getSingleChar(this.buffer, this.pointer + index);
  62. }
  63. return data;
  64. };
  65. Reader.prototype.prefix = function prefix(length) {
  66. length = +length || 1;
  67. if (this.pointer + length >= this.buffer.length) {
  68. this.update(length);
  69. }
  70. return this.buffer.slice(this.pointer, this.pointer + length);
  71. };
  72. Reader.prototype.forward = function forward(length) {
  73. var ch;
  74. // WARNING!!! length default is <int:1>, but method cn be called with
  75. // <int:0> which is absolutely NOT default length value, so
  76. // that's why we have ternary operator instead of lazy assign.
  77. length = (undefined !== length) ? (+length) : 1;
  78. if (this.pointer + length + 1 >= this.buffer.length) {
  79. this.update(length + 1);
  80. }
  81. while (length) {
  82. ch = this.buffer[this.pointer];
  83. this.pointer += 1;
  84. this.index += 1;
  85. if (0 <= '\n\x85\u2028\u2029'.indexOf(ch)
  86. || ('\r' === ch && '\n' !== this.buffer[this.pointer])) {
  87. this.line += 1;
  88. this.column = 0;
  89. } else if (ch !== '\uFEFF') {
  90. this.column += 1;
  91. }
  92. length -= 1;
  93. }
  94. };
  95. Reader.prototype.getMark = function getMark() {
  96. if (null === this.stream) {
  97. return new _errors.Mark(this.name, this.index, this.line, this.column,
  98. this.buffer, this.pointer);
  99. } else {
  100. return new _errors.Mark(this.name, this.index, this.line, this.column,
  101. null, null);
  102. }
  103. };
  104. Reader.prototype.checkPrintable = function checkPrintable(data) {
  105. var match = data.toString().match(NON_PRINTABLE), position;
  106. if (match) {
  107. position = this.index + this.buffer.length - this.pointer + match.index;
  108. throw new ReaderError(this.name, position, match[0],
  109. 'unicode', 'special characters are not allowed');
  110. }
  111. };
  112. Reader.prototype.update = function update(length) {
  113. var data;
  114. if (null === this.rawBuffer) {
  115. return;
  116. }
  117. this.buffer = this.buffer.slice(this.pointer);
  118. this.pointer = 0;
  119. while (this.buffer.length < length) {
  120. if (!this.eof) {
  121. this.updateRaw();
  122. }
  123. data = this.rawBuffer;
  124. this.checkPrintable(data);
  125. this.buffer += data;
  126. this.rawBuffer = this.rawBuffer.slice(data.length);
  127. if (this.eof) {
  128. this.buffer += '\x00';
  129. this.rawBuffer = null;
  130. break;
  131. }
  132. }
  133. };
  134. Reader.prototype.updateRaw = function updateRaw(size) {
  135. var data = new Buffer(+size || 4096), count, tmp;
  136. count = fs.readSync(this.stream, data, 0, data.length);
  137. if (null === this.rawBuffer) {
  138. this.rawBuffer = data.slice(0, count);
  139. } else {
  140. tmp = new Buffer(this.rawBuffer.length + count);
  141. this.rawBuffer.copy(tmp);
  142. data.copy(tmp, this.rawBuffer.length);
  143. this.rawBuffer = tmp;
  144. }
  145. this.streamPointer += count;
  146. if (!count || count < data.length) {
  147. this.eof = true;
  148. }
  149. };
  150. module.exports.Reader = Reader;
  151. ////////////////////////////////////////////////////////////////////////////////
  152. // vim:ts=2:sw=2
  153. ////////////////////////////////////////////////////////////////////////////////