style-reader.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. var _ = require("underscore");
  2. var lop = require("lop");
  3. var documentMatchers = require("./styles/document-matchers");
  4. var htmlPaths = require("./styles/html-paths");
  5. var tokenise = require("./styles/parser/tokeniser").tokenise;
  6. var results = require("./results");
  7. exports.readHtmlPath = readHtmlPath;
  8. exports.readDocumentMatcher = readDocumentMatcher;
  9. exports.readStyle = readStyle;
  10. function readStyle(string) {
  11. return parseString(styleRule, string);
  12. }
  13. function createStyleRule() {
  14. return lop.rules.sequence(
  15. lop.rules.sequence.capture(documentMatcherRule()),
  16. lop.rules.tokenOfType("whitespace"),
  17. lop.rules.tokenOfType("arrow"),
  18. lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(
  19. lop.rules.tokenOfType("whitespace"),
  20. lop.rules.sequence.capture(htmlPathRule())
  21. ).head())),
  22. lop.rules.tokenOfType("end")
  23. ).map(function(documentMatcher, htmlPath) {
  24. return {
  25. from: documentMatcher,
  26. to: htmlPath.valueOrElse(htmlPaths.empty)
  27. };
  28. });
  29. }
  30. function readDocumentMatcher(string) {
  31. return parseString(documentMatcherRule(), string);
  32. }
  33. function documentMatcherRule() {
  34. var sequence = lop.rules.sequence;
  35. var identifierToConstant = function(identifier, constant) {
  36. return lop.rules.then(
  37. lop.rules.token("identifier", identifier),
  38. function() {
  39. return constant;
  40. }
  41. );
  42. };
  43. var paragraphRule = identifierToConstant("p", documentMatchers.paragraph);
  44. var runRule = identifierToConstant("r", documentMatchers.run);
  45. var elementTypeRule = lop.rules.firstOf("p or r or table",
  46. paragraphRule,
  47. runRule
  48. );
  49. var styleIdRule = lop.rules.sequence(
  50. lop.rules.tokenOfType("dot"),
  51. lop.rules.sequence.cut(),
  52. lop.rules.sequence.capture(identifierRule)
  53. ).map(function(styleId) {
  54. return {styleId: styleId};
  55. });
  56. var styleNameMatcherRule = lop.rules.firstOf("style name matcher",
  57. lop.rules.then(
  58. lop.rules.sequence(
  59. lop.rules.tokenOfType("equals"),
  60. lop.rules.sequence.cut(),
  61. lop.rules.sequence.capture(stringRule)
  62. ).head(),
  63. function(styleName) {
  64. return {styleName: documentMatchers.equalTo(styleName)};
  65. }
  66. ),
  67. lop.rules.then(
  68. lop.rules.sequence(
  69. lop.rules.tokenOfType("startsWith"),
  70. lop.rules.sequence.cut(),
  71. lop.rules.sequence.capture(stringRule)
  72. ).head(),
  73. function(styleName) {
  74. return {styleName: documentMatchers.startsWith(styleName)};
  75. }
  76. )
  77. );
  78. var styleNameRule = lop.rules.sequence(
  79. lop.rules.tokenOfType("open-square-bracket"),
  80. lop.rules.sequence.cut(),
  81. lop.rules.token("identifier", "style-name"),
  82. lop.rules.sequence.capture(styleNameMatcherRule),
  83. lop.rules.tokenOfType("close-square-bracket")
  84. ).head();
  85. var listTypeRule = lop.rules.firstOf("list type",
  86. identifierToConstant("ordered-list", {isOrdered: true}),
  87. identifierToConstant("unordered-list", {isOrdered: false})
  88. );
  89. var listRule = sequence(
  90. lop.rules.tokenOfType("colon"),
  91. sequence.capture(listTypeRule),
  92. sequence.cut(),
  93. lop.rules.tokenOfType("open-paren"),
  94. sequence.capture(integerRule),
  95. lop.rules.tokenOfType("close-paren")
  96. ).map(function(listType, levelNumber) {
  97. return {
  98. list: {
  99. isOrdered: listType.isOrdered,
  100. levelIndex: levelNumber - 1
  101. }
  102. };
  103. });
  104. function createMatcherSuffixesRule(rules) {
  105. var matcherSuffix = lop.rules.firstOf.apply(
  106. lop.rules.firstOf,
  107. ["matcher suffix"].concat(rules)
  108. );
  109. var matcherSuffixes = lop.rules.zeroOrMore(matcherSuffix);
  110. return lop.rules.then(matcherSuffixes, function(suffixes) {
  111. var matcherOptions = {};
  112. suffixes.forEach(function(suffix) {
  113. _.extend(matcherOptions, suffix);
  114. });
  115. return matcherOptions;
  116. });
  117. }
  118. var paragraphOrRun = sequence(
  119. sequence.capture(elementTypeRule),
  120. sequence.capture(createMatcherSuffixesRule([
  121. styleIdRule,
  122. styleNameRule,
  123. listRule
  124. ]))
  125. ).map(function(createMatcher, matcherOptions) {
  126. return createMatcher(matcherOptions);
  127. });
  128. var table = sequence(
  129. lop.rules.token("identifier", "table"),
  130. sequence.capture(createMatcherSuffixesRule([
  131. styleIdRule,
  132. styleNameRule
  133. ]))
  134. ).map(function(options) {
  135. return documentMatchers.table(options);
  136. });
  137. var bold = identifierToConstant("b", documentMatchers.bold);
  138. var italic = identifierToConstant("i", documentMatchers.italic);
  139. var underline = identifierToConstant("u", documentMatchers.underline);
  140. var strikethrough = identifierToConstant("strike", documentMatchers.strikethrough);
  141. var allCaps = identifierToConstant("all-caps", documentMatchers.allCaps);
  142. var smallCaps = identifierToConstant("small-caps", documentMatchers.smallCaps);
  143. var highlight = sequence(
  144. lop.rules.token("identifier", "highlight"),
  145. lop.rules.sequence.capture(lop.rules.optional(lop.rules.sequence(
  146. lop.rules.tokenOfType("open-square-bracket"),
  147. lop.rules.sequence.cut(),
  148. lop.rules.token("identifier", "color"),
  149. lop.rules.tokenOfType("equals"),
  150. lop.rules.sequence.capture(stringRule),
  151. lop.rules.tokenOfType("close-square-bracket")
  152. ).head()))
  153. ).map(function(color) {
  154. return documentMatchers.highlight({
  155. color: color.valueOrElse(undefined)
  156. });
  157. });
  158. var commentReference = identifierToConstant("comment-reference", documentMatchers.commentReference);
  159. var breakMatcher = sequence(
  160. lop.rules.token("identifier", "br"),
  161. sequence.cut(),
  162. lop.rules.tokenOfType("open-square-bracket"),
  163. lop.rules.token("identifier", "type"),
  164. lop.rules.tokenOfType("equals"),
  165. sequence.capture(stringRule),
  166. lop.rules.tokenOfType("close-square-bracket")
  167. ).map(function(breakType) {
  168. switch (breakType) {
  169. case "line":
  170. return documentMatchers.lineBreak;
  171. case "page":
  172. return documentMatchers.pageBreak;
  173. case "column":
  174. return documentMatchers.columnBreak;
  175. default:
  176. // TODO: handle unknown document matchers
  177. }
  178. });
  179. return lop.rules.firstOf("element type",
  180. paragraphOrRun,
  181. table,
  182. bold,
  183. italic,
  184. underline,
  185. strikethrough,
  186. allCaps,
  187. smallCaps,
  188. highlight,
  189. commentReference,
  190. breakMatcher
  191. );
  192. }
  193. function readHtmlPath(string) {
  194. return parseString(htmlPathRule(), string);
  195. }
  196. function htmlPathRule() {
  197. var capture = lop.rules.sequence.capture;
  198. var whitespaceRule = lop.rules.tokenOfType("whitespace");
  199. var freshRule = lop.rules.then(
  200. lop.rules.optional(lop.rules.sequence(
  201. lop.rules.tokenOfType("colon"),
  202. lop.rules.token("identifier", "fresh")
  203. )),
  204. function(option) {
  205. return option.map(function() {
  206. return true;
  207. }).valueOrElse(false);
  208. }
  209. );
  210. var separatorRule = lop.rules.then(
  211. lop.rules.optional(lop.rules.sequence(
  212. lop.rules.tokenOfType("colon"),
  213. lop.rules.token("identifier", "separator"),
  214. lop.rules.tokenOfType("open-paren"),
  215. capture(stringRule),
  216. lop.rules.tokenOfType("close-paren")
  217. ).head()),
  218. function(option) {
  219. return option.valueOrElse("");
  220. }
  221. );
  222. var tagNamesRule = lop.rules.oneOrMoreWithSeparator(
  223. identifierRule,
  224. lop.rules.tokenOfType("choice")
  225. );
  226. var styleElementRule = lop.rules.sequence(
  227. capture(tagNamesRule),
  228. capture(lop.rules.zeroOrMore(attributeOrClassRule)),
  229. capture(freshRule),
  230. capture(separatorRule)
  231. ).map(function(tagName, attributesList, fresh, separator) {
  232. var attributes = {};
  233. var options = {};
  234. attributesList.forEach(function(attribute) {
  235. if (attribute.append && attributes[attribute.name]) {
  236. attributes[attribute.name] += " " + attribute.value;
  237. } else {
  238. attributes[attribute.name] = attribute.value;
  239. }
  240. });
  241. if (fresh) {
  242. options.fresh = true;
  243. }
  244. if (separator) {
  245. options.separator = separator;
  246. }
  247. return htmlPaths.element(tagName, attributes, options);
  248. });
  249. return lop.rules.firstOf("html path",
  250. lop.rules.then(lop.rules.tokenOfType("bang"), function() {
  251. return htmlPaths.ignore;
  252. }),
  253. lop.rules.then(
  254. lop.rules.zeroOrMoreWithSeparator(
  255. styleElementRule,
  256. lop.rules.sequence(
  257. whitespaceRule,
  258. lop.rules.tokenOfType("gt"),
  259. whitespaceRule
  260. )
  261. ),
  262. htmlPaths.elements
  263. )
  264. );
  265. }
  266. var identifierRule = lop.rules.then(
  267. lop.rules.tokenOfType("identifier"),
  268. decodeEscapeSequences
  269. );
  270. var integerRule = lop.rules.tokenOfType("integer");
  271. var stringRule = lop.rules.then(
  272. lop.rules.tokenOfType("string"),
  273. decodeEscapeSequences
  274. );
  275. var escapeSequences = {
  276. "n": "\n",
  277. "r": "\r",
  278. "t": "\t"
  279. };
  280. function decodeEscapeSequences(value) {
  281. return value.replace(/\\(.)/g, function(match, code) {
  282. return escapeSequences[code] || code;
  283. });
  284. }
  285. var attributeRule = lop.rules.sequence(
  286. lop.rules.tokenOfType("open-square-bracket"),
  287. lop.rules.sequence.cut(),
  288. lop.rules.sequence.capture(identifierRule),
  289. lop.rules.tokenOfType("equals"),
  290. lop.rules.sequence.capture(stringRule),
  291. lop.rules.tokenOfType("close-square-bracket")
  292. ).map(function(name, value) {
  293. return {name: name, value: value, append: false};
  294. });
  295. var classRule = lop.rules.sequence(
  296. lop.rules.tokenOfType("dot"),
  297. lop.rules.sequence.cut(),
  298. lop.rules.sequence.capture(identifierRule)
  299. ).map(function(className) {
  300. return {name: "class", value: className, append: true};
  301. });
  302. var attributeOrClassRule = lop.rules.firstOf(
  303. "attribute or class",
  304. attributeRule,
  305. classRule
  306. );
  307. function parseString(rule, string) {
  308. var tokens = tokenise(string);
  309. var parser = lop.Parser();
  310. var parseResult = parser.parseTokens(rule, tokens);
  311. if (parseResult.isSuccess()) {
  312. return results.success(parseResult.value());
  313. } else {
  314. return new results.Result(null, [results.warning(describeFailure(string, parseResult))]);
  315. }
  316. }
  317. function describeFailure(input, parseResult) {
  318. return "Did not understand this style mapping, so ignored it: " + input + "\n" +
  319. parseResult.errors().map(describeError).join("\n");
  320. }
  321. function describeError(error) {
  322. return "Error was at character number " + error.characterNumber() + ": " +
  323. "Expected " + error.expected + " but got " + error.actual;
  324. }
  325. var styleRule = createStyleRule();