20 |
21 | ___________________________
22 | < I'm an expert in my field. >
23 | ---------------------------
24 | \ ^__^
25 | \ (oo)\_______
26 | (__)\ )\/\
27 | ||----w |
28 | || ||
29 |
30 |
31 | A cow saying, "I'm an expert in my field." The cow is illustrated using preformatted text characters.
32 |
33 |
34 | ```
35 |
36 | **yourHtmlFile.nim**
37 |
38 | ```nim
39 | include karax / prelude
40 |
41 | proc createDom(): VNode =
42 | result = buildHtml:
43 | figure(role = "img", aria-labelledby = "cow-caption"):
44 | pre:
45 | text """
46 | ___________________________
47 | < I'm an expert in my field. >
48 | ---------------------------
49 | \ ^__^
50 | \ (oo)\_______
51 | (__)\ )\/\
52 | ||----w |
53 | || ||
54 | """
55 | figcaption(id = "cow-caption"):
56 | text "A cow saying, \"I'm an expert in my field.\" The cow is illustrated using "
57 | em:
58 | text "preformatted"
59 | text " text characters."
60 |
61 | setRenderer createDom
62 | ```
63 |
--------------------------------------------------------------------------------
/html2karax.nim:
--------------------------------------------------------------------------------
1 | import std / [algorithm, htmlparser, parseopt, strtabs, strutils, os, xmltree, wordwrap]
2 |
3 | const
4 | usage = """
5 | html2karax - Convert static html to Karax DSL code.
6 |
7 | Usage:
8 | html2karax [options] htmlfile
9 | Options:
10 | --out:file set the output file (default: the same name as the input file, .nim extension)
11 | --help show this help
12 | --ssr output code appropriate for server side HTML rendering
13 | --indent:N[=2] set the number of spaces that is used for indentation
14 | --maxLineLen:N set the desired maximum line length (default: 80)
15 | """
16 |
17 | karaxTmpl = """
18 | include karax / prelude
19 | import karax / [vstyles]
20 |
21 | proc createDom(): VNode =
22 | result = buildHtml:$1
23 |
24 | setRenderer createDom
25 | """
26 |
27 | karaxSsrTmpl = """
28 | import karax / [karaxdsl, vdom, vstyles]
29 |
30 | proc render(): string =
31 | let vnode = buildHtml:$1
32 | result = $$vnode
33 | """
34 |
35 | nimKeywords = ["addr", "and", "as", "asm",
36 | "bind", "block", "break",
37 | "case", "cast", "concept", "const", "continue", "converter",
38 | "defer", "discard", "distinct", "div", "do",
39 | "elif", "else", "end", "enum", "except", "export",
40 | "finally", "for", "from", "func",
41 | "if", "import", "in", "include", "interface", "is", "isnot", "iterator",
42 | "let",
43 | "macro", "method", "mixin", "mod",
44 | "nil", "not", "notin",
45 | "object", "of", "or", "out",
46 | "proc", "ptr",
47 | "raise", "ref", "return",
48 | "shl", "shr", "static",
49 | "template", "try", "tuple", "type",
50 | "using",
51 | "var",
52 | "when", "while",
53 | "xor",
54 | "yield"]
55 |
56 | proc isKeyword*(s: string): bool {.inline.} =
57 | binarySearch(nimKeywords, s) >= 0 # assumes sorted
58 |
59 | type
60 | Options = object # from command-line
61 | indWidth: Natural
62 | maxLineLen: Positive
63 |
64 | proc addVNode(result: var string; tag: string) =
65 | # Translates to karax VNode tags, most remain the same.
66 | case tag
67 | of "div":
68 | result.add "tdiv"
69 | of "s":
70 | result.add "strikethrough"
71 | of "var":
72 | result.add "`var`"
73 | of "i":
74 | result.add "italic"
75 | of "b":
76 | result.add "bold"
77 | of "u":
78 | result.add "underlined"
79 | of "object":
80 | result.add "`object`"
81 | of "discard":
82 | result.add "`discard`"
83 | of "set":
84 | result.add "`set`"
85 | of "text":
86 | result.add "stext"
87 | else:
88 | result.add tag
89 |
90 | proc addIndent(result: var string, indent: int) =
91 | result.add("\n")
92 | for i in 1..indent:
93 | result.add(' ')
94 |
95 | proc myRender(result: var string, b: string, escapeQuotes, stripSpaces: bool) =
96 | # Primarily used to remove spaces at line end. Also escapes '"' and
97 | # omits in-between multiple spaces when appropriate.
98 | let L = b.len
99 | var i = 0
100 | while i < L:
101 | let ch = b[i]
102 | if escapeQuotes and ch == '\"':
103 | result.add("\\\"")
104 | elif ch == ' ':
105 | let j = i
106 | while i < L and b[i] == ' ': inc i
107 | if i >= L: discard
108 | elif b[i] == '\n':
109 | result.add('\n')
110 | else:
111 | if stripSpaces: result.add(' ')
112 | else:
113 | for ii in j..i-1:
114 | result.add(' ')
115 | dec i
116 | elif ch == '\n':
117 | result.add('\n')
118 | else:
119 | result.add(ch)
120 | inc(i)
121 |
122 | proc renderText(result: var string, text: string;
123 | spaceInsensitive, leadingSpace, trailingSpace: bool) =
124 | let isSingleLine = countLines(text) == 1
125 | if isSingleLine:
126 | result.add('"')
127 | else:
128 | result.add("\"\"\"")
129 | if spaceInsensitive: result.add('\n') # verbatim multiline text that starts with a '\n'
130 | if leadingSpace: result.add(' ')
131 | myRender(result, text, isSingleLine, spaceInsensitive)
132 | if trailingSpace: result.add(' ')
133 | if isSingleLine:
134 | result.add('"')
135 | else:
136 | result.add("\"\"\"")
137 |
138 | proc renderBacklog(result: var string, text: string, indent: int, tags: set[HtmlTag];
139 | last: bool; opt: Options) =
140 | if indent > 0:
141 | result.addIndent(indent)
142 | result.add("text ")
143 | var tmp = if tagPre notin tags: text.dedent else: text
144 | if tags * {tagScript, tagPre} != {}:
145 | renderText(result, tmp, spaceInsensitive = false, false, false)
146 | else:
147 | removePrefix(tmp, chars = Newlines) # fix leading \n replaced by ' '
148 | var wrapped = wrapWords(tmp, opt.maxLineLen, splitLongWords = false)
149 | let leadingSpace = text.startsWith(' ') and not tmp.startsWith(' ')
150 | # Use that a block element can't be nested inside an inline element
151 | let trailingSpace = tmp.endsWith(' ') and (tags * InlineTags != {} or last)
152 | renderText(result, wrapped, true, leadingSpace, trailingSpace) # readd surrounding spaces
153 |
154 | proc renderImpl(result: var string, n: XmlNode, backlog: var string, indent: int;
155 | tags: set[HtmlTag]; opt: Options) =
156 | if n != nil:
157 | case n.kind
158 | of xnElement:
159 | let tag = htmlTag(n)
160 | let isDocRoot = tag == tagUnknown and n.tag == "document" # Hide document pseudo-tag
161 | if not isDocRoot:
162 | if indent > 0:
163 | result.addIndent(indent)
164 | result.addVNode(n.tag)
165 | if n.attrs != nil:
166 | result.add('(')
167 | var comma = false
168 | for key, val in pairs(n.attrs):
169 | if comma: result.add(", ")
170 | else: comma = true
171 | let isKeyword = isKeyword(key)
172 | if isKeyword:
173 | result.add('`')
174 | result.add(key)
175 | if isKeyword:
176 | result.add('`')
177 | result.add(" = \"")
178 | #myRender(result, val, true)
179 | result.add(val)
180 | result.add('"')
181 | if key == "style":
182 | result.add(".toCss")
183 | result.add(')')
184 | elif n.len == 0: # An empty element without attributes
185 | result.add("()")
186 | if n.len != 0:
187 | if not isDocRoot: result.add(':')
188 | let indent = if isDocRoot: indent else: indent+opt.indWidth
189 | for i in 0 ..< n.len:
190 | renderImpl(result, n[i], backlog, indent, tags + {tag}, opt)
191 | if i+1 >= n.len or n[i+1].kind != xnText: # Invalidate the backlog
192 | # Render grouped text nodes, without outputting empty text.
193 | if not isEmptyOrWhitespace(backlog):
194 | renderBacklog(result, backlog, indent, tags + {tag}, i+1 < n.len, opt)
195 | backlog.setLen(0)
196 | of xnText:
197 | backlog.add n.text
198 | of xnComment:
199 | if not isEmptyOrWhitespace(n.text):
200 | if indent > 0: # All comments are indented
201 | result.addIndent(indent)
202 | if countLines(n.text) == 1:
203 | result.add('#')
204 | myRender(result, n.text, escapeQuotes = false, stripSpaces = false)
205 | else:
206 | result.add("#[")
207 | # Unindent text before indenting it again!
208 | myRender(result, indent(n.text.dedent, indent), false, false)
209 | stripLineEnd(result) # comment end tag in the next line
210 | if indent > 0:
211 | result.addIndent(indent)
212 | result.add("]#")
213 | else: discard
214 |
215 | proc render(n: XmlNode, indent = 0, opt: Options): string =
216 | result = ""
217 | var backlog = ""
218 | renderImpl(result, n, backlog, indent, {}, opt)
219 |
220 | proc writeHelp() =
221 | stdout.write(usage)
222 | stdout.flushFile()
223 | quit(0)
224 |
225 | proc main =
226 | var infile, outfile: string
227 | var ssr = false
228 | var opt = Options(indWidth: 2, maxLineLen: 80)
229 | for kind, key, val in getopt():
230 | case kind
231 | of cmdArgument:
232 | infile = key.addFileExt(".html")
233 | of cmdLongOption, cmdShortOption:
234 | case normalize(key)
235 | of "help", "h": writeHelp()
236 | of "output", "o", "out": outfile = val
237 | of "ssr": ssr = true
238 | of "indent": opt.indWidth = parseInt(val)
239 | of "maxlinelen": opt.maxLineLen = parseInt(val)
240 | else: writeHelp()
241 | of cmdEnd: assert false # cannot happen
242 |
243 | if infile.len == 0:
244 | quit "[Error] no input file."
245 |
246 | if outfile.len == 0:
247 | outfile = infile.changeFileExt(".nim")
248 |
249 | let parsed = loadHtml(infile)
250 | let result = render(parsed, 2*opt.indWidth, opt) # Templates start with the same indentation
251 | writeFile(outfile, if ssr: karaxSsrTmpl % result else: karaxTmpl % result)
252 |
253 | main()
254 |
--------------------------------------------------------------------------------
/html2karax.nimble:
--------------------------------------------------------------------------------
1 | # Package
2 |
3 | version = "1.2.1"
4 | author = "nim-lang-cn"
5 | description = "Converts html to karax."
6 | license = "MIT"
7 | bin = @["html2karax"]
8 | skipDirs = @["public","tests"]
9 |
10 | # Dependencies
11 |
12 | requires "nim >= 1.6.4"
13 | requires "karax >= 1.2.1"
14 |
--------------------------------------------------------------------------------
/public/README.md:
--------------------------------------------------------------------------------
1 | # html2karax
2 | Convert static html to Karax single page application or server sider rendering.Use https://github.com/gogolxdong/karax
3 |
4 | 把静态html转换为Karax单面应用或服务端渲染,使用https://github.com/gogolxdong/karax
5 |
6 |
7 | ## Usage
8 | nim c -r html2karax.nim tut1
9 | 把tut1.html转换成tut1karax.nim
10 |
11 | nim c -r html2karax.nim tut2
12 | 把tut2.html转换成tut2karax.nim
13 |
14 | nim c -r html2karax.nim tut3
15 | 把tut3.html转换成tut3karax.nim
16 |
17 | nim c -r html2karax.nim manual
18 | 把manual.html转换成manualkarax.nim
19 |
20 | nim js tut1karax.nim
21 | 把tut1karax.nim编译成js,以此类推。
22 |
23 | Open browser and access html accordingly.
24 | 通过浏览器访问相应的html文件
25 |
--------------------------------------------------------------------------------
/public/config.nims:
--------------------------------------------------------------------------------
1 | --define:release
2 | --hints:off
3 | --warnings:off
--------------------------------------------------------------------------------
/public/dochack.nim:
--------------------------------------------------------------------------------
1 | import dom
2 | import fuzzysearch
3 |
4 | proc textContent(e: Element): cstring {.
5 | importcpp: "#.textContent", nodecl.}
6 |
7 | proc textContent(e: Node): cstring {.
8 | importcpp: "#.textContent", nodecl.}
9 |
10 | proc tree(tag: string; kids: varargs[Element]): Element =
11 | result = document.createElement tag
12 | for k in kids:
13 | result.appendChild k
14 |
15 | proc add(parent, kid: Element) =
16 | if parent.nodeName == cstring"TR" and (
17 | kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"):
18 | let k = document.createElement("TD")
19 | appendChild(k, kid)
20 | appendChild(parent, k)
21 | else:
22 | appendChild(parent, kid)
23 |
24 | proc setClass(e: Element; value: string) =
25 | e.setAttribute("class", value)
26 | proc text(s: string): Element = cast[Element](document.createTextNode(s))
27 | proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
28 |
29 | proc getElementById(id: cstring): Element {.importc: "document.getElementById", nodecl.}
30 |
31 | proc replaceById(id: cstring; newTree: Node) =
32 | let x = getElementById(id)
33 | x.parentNode.replaceChild(newTree, x)
34 | newTree.id = id
35 |
36 | proc findNodeWith(x: Element; tag, content: cstring): Element =
37 | if x.nodeName == tag and x.textContent == content:
38 | return x
39 | for i in 0..