You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

282 lines
5.0 KiB

  1. package parser
  2. import "regexp"
  3. import "strings"
  4. var selfClosingTags = [...]string{
  5. "meta",
  6. "img",
  7. "link",
  8. "input",
  9. "source",
  10. "area",
  11. "base",
  12. "col",
  13. "br",
  14. "hr",
  15. }
  16. var doctypes = map[string]string{
  17. "5": `<!DOCTYPE html>`,
  18. "default": `<!DOCTYPE html>`,
  19. "xml": `<?xml version="1.0" encoding="utf-8" ?>`,
  20. "transitional": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">`,
  21. "strict": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">`,
  22. "frameset": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">`,
  23. "1.1": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">`,
  24. "basic": `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic11.dtd">`,
  25. "mobile": `<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" "http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">`,
  26. }
  27. type Node interface {
  28. Pos() SourcePosition
  29. }
  30. type SourcePosition struct {
  31. LineNum int
  32. ColNum int
  33. TokenLength int
  34. Filename string
  35. }
  36. func (s *SourcePosition) Pos() SourcePosition {
  37. return *s
  38. }
  39. type Doctype struct {
  40. SourcePosition
  41. Value string
  42. }
  43. func newDoctype(value string) *Doctype {
  44. dt := new(Doctype)
  45. dt.Value = value
  46. return dt
  47. }
  48. func (d *Doctype) String() string {
  49. if defined := doctypes[d.Value]; len(defined) != 0 {
  50. return defined
  51. }
  52. return `<!DOCTYPE ` + d.Value + `>`
  53. }
  54. type Comment struct {
  55. SourcePosition
  56. Value string
  57. Block *Block
  58. Silent bool
  59. }
  60. func newComment(value string) *Comment {
  61. dt := new(Comment)
  62. dt.Value = value
  63. dt.Block = nil
  64. dt.Silent = false
  65. return dt
  66. }
  67. type Text struct {
  68. SourcePosition
  69. Value string
  70. Raw bool
  71. }
  72. func newText(value string, raw bool) *Text {
  73. dt := new(Text)
  74. dt.Value = value
  75. dt.Raw = raw
  76. return dt
  77. }
  78. type Block struct {
  79. SourcePosition
  80. Children []Node
  81. }
  82. func newBlock() *Block {
  83. block := new(Block)
  84. block.Children = make([]Node, 0)
  85. return block
  86. }
  87. func (b *Block) push(node Node) {
  88. b.Children = append(b.Children, node)
  89. }
  90. func (b *Block) pushFront(node Node) {
  91. b.Children = append([]Node{node}, b.Children...)
  92. }
  93. func (b *Block) CanInline() bool {
  94. if len(b.Children) == 0 {
  95. return true
  96. }
  97. allText := true
  98. for _, child := range b.Children {
  99. if txt, ok := child.(*Text); !ok || txt.Raw {
  100. allText = false
  101. break
  102. }
  103. }
  104. return allText
  105. }
  106. const (
  107. NamedBlockDefault = iota
  108. NamedBlockAppend
  109. NamedBlockPrepend
  110. )
  111. type NamedBlock struct {
  112. Block
  113. Name string
  114. Modifier int
  115. }
  116. func newNamedBlock(name string) *NamedBlock {
  117. bb := new(NamedBlock)
  118. bb.Name = name
  119. bb.Block.Children = make([]Node, 0)
  120. bb.Modifier = NamedBlockDefault
  121. return bb
  122. }
  123. type Attribute struct {
  124. SourcePosition
  125. Name string
  126. Value string
  127. IsRaw bool
  128. Condition string
  129. }
  130. type Tag struct {
  131. SourcePosition
  132. Block *Block
  133. Name string
  134. IsInterpolated bool
  135. Attributes []Attribute
  136. }
  137. func newTag(name string) *Tag {
  138. tag := new(Tag)
  139. tag.Block = nil
  140. tag.Name = name
  141. tag.Attributes = make([]Attribute, 0)
  142. tag.IsInterpolated = false
  143. return tag
  144. }
  145. func (t *Tag) IsSelfClosing() bool {
  146. for _, tag := range selfClosingTags {
  147. if tag == t.Name {
  148. return true
  149. }
  150. }
  151. return false
  152. }
  153. func (t *Tag) IsRawText() bool {
  154. return t.Name == "style" || t.Name == "script"
  155. }
  156. type Condition struct {
  157. SourcePosition
  158. Positive *Block
  159. Negative *Block
  160. Expression string
  161. }
  162. func newCondition(exp string) *Condition {
  163. cond := new(Condition)
  164. cond.Expression = exp
  165. return cond
  166. }
  167. type Each struct {
  168. SourcePosition
  169. X string
  170. Y string
  171. Expression string
  172. Block *Block
  173. }
  174. func newEach(exp string) *Each {
  175. each := new(Each)
  176. each.Expression = exp
  177. return each
  178. }
  179. type Assignment struct {
  180. SourcePosition
  181. X string
  182. Expression string
  183. }
  184. func newAssignment(x, expression string) *Assignment {
  185. assgn := new(Assignment)
  186. assgn.X = x
  187. assgn.Expression = expression
  188. return assgn
  189. }
  190. type Mixin struct {
  191. SourcePosition
  192. Block *Block
  193. Name string
  194. Args []string
  195. }
  196. func newMixin(name, args string) *Mixin {
  197. mixin := new(Mixin)
  198. mixin.Name = name
  199. delExp := regexp.MustCompile(`,\s`)
  200. mixin.Args = delExp.Split(args, -1)
  201. for i := 0; i < len(mixin.Args); i++ {
  202. mixin.Args[i] = strings.TrimSpace(mixin.Args[i])
  203. if mixin.Args[i] == "" {
  204. mixin.Args = append(mixin.Args[:i], mixin.Args[i+1:]...)
  205. i--
  206. }
  207. }
  208. return mixin
  209. }
  210. type MixinCall struct {
  211. SourcePosition
  212. Name string
  213. Args []string
  214. }
  215. func newMixinCall(name, args string) *MixinCall {
  216. mixinCall := new(MixinCall)
  217. mixinCall.Name = name
  218. const t = "%s"
  219. quoteExp := regexp.MustCompile(`"(.*?)"`)
  220. delExp := regexp.MustCompile(`,\s`)
  221. quotes := quoteExp.FindAllString(args, -1)
  222. replaced := quoteExp.ReplaceAllString(args, t)
  223. mixinCall.Args = delExp.Split(replaced, -1)
  224. qi := 0
  225. for i, arg := range mixinCall.Args {
  226. if arg == t {
  227. mixinCall.Args[i] = quotes[qi]
  228. qi++
  229. }
  230. }
  231. return mixinCall
  232. }