| 37 |  |  | // element with an  child, and is therefore not 'well-formed'.
 | 
      
         | 38 |  |  | //
 | 
      
         | 39 |  |  | // Programmatically constructed trees are typically also 'well-formed', but it
 | 
      
         | 40 |  |  | // is possible to construct a tree that looks innocuous but, when rendered and
 | 
      
         | 41 |  |  | // re-parsed, results in a different tree. A simple example is that a solitary
 | 
      
         | 42 |  |  | // text node would become a tree containing ,  and  elements.
 | 
      
         | 43 |  |  | // Another example is that the programmatic equivalent of "abc"
 | 
      
         | 44 |  |  | // becomes "abc".
 | 
      
         | 45 |  |  | func Render(w io.Writer, n *Node) error {
 | 
      
         | 46 |  |  |         if x, ok := w.(writer); ok {
 | 
      
         | 47 |  |  |                 return render(x, n)
 | 
      
         | 48 |  |  |         }
 | 
      
         | 49 |  |  |         buf := bufio.NewWriter(w)
 | 
      
         | 50 |  |  |         if err := render(buf, n); err != nil {
 | 
      
         | 51 |  |  |                 return err
 | 
      
         | 52 |  |  |         }
 | 
      
         | 53 |  |  |         return buf.Flush()
 | 
      
         | 54 |  |  | }
 | 
      
         | 55 |  |  |  
 | 
      
         | 56 |  |  | // plaintextAbort is returned from render1 when a  element
 | 
      
         | 57 |  |  | // has been rendered. No more end tags should be rendered after that.
 | 
      
         | 58 |  |  | var plaintextAbort = errors.New("html: internal error (plaintext abort)")
 | 
      
         | 59 |  |  |  
 | 
      
         | 60 |  |  | func render(w writer, n *Node) error {
 | 
      
         | 61 |  |  |         err := render1(w, n)
 | 
      
         | 62 |  |  |         if err == plaintextAbort {
 | 
      
         | 63 |  |  |                 err = nil
 | 
      
         | 64 |  |  |         }
 | 
      
         | 65 |  |  |         return err
 | 
      
         | 66 |  |  | }
 | 
      
         | 67 |  |  |  
 | 
      
         | 68 |  |  | func render1(w writer, n *Node) error {
 | 
      
         | 69 |  |  |         // Render non-element nodes; these are the easy cases.
 | 
      
         | 70 |  |  |         switch n.Type {
 | 
      
         | 71 |  |  |         case ErrorNode:
 | 
      
         | 72 |  |  |                 return errors.New("html: cannot render an ErrorNode node")
 | 
      
         | 73 |  |  |         case TextNode:
 | 
      
         | 74 |  |  |                 return escape(w, n.Data)
 | 
      
         | 75 |  |  |         case DocumentNode:
 | 
      
         | 76 |  |  |                 for _, c := range n.Child {
 | 
      
         | 77 |  |  |                         if err := render1(w, c); err != nil {
 | 
      
         | 78 |  |  |                                 return err
 | 
      
         | 79 |  |  |                         }
 | 
      
         | 80 |  |  |                 }
 | 
      
         | 81 |  |  |                 return nil
 | 
      
         | 82 |  |  |         case ElementNode:
 | 
      
         | 83 |  |  |                 // No-op.
 | 
      
         | 84 |  |  |         case CommentNode:
 | 
      
         | 85 |  |  |                 if _, err := w.WriteString(""); err != nil {
 | 
      
         | 92 |  |  |                         return err
 | 
      
         | 93 |  |  |                 }
 | 
      
         | 94 |  |  |                 return nil
 | 
      
         | 95 |  |  |         case DoctypeNode:
 | 
      
         | 96 |  |  |                 if _, err := w.WriteString("
 | 
      
         | 97 |  |  |                         return err
 | 
      
         | 98 |  |  |                 }
 | 
      
         | 99 |  |  |                 if _, err := w.WriteString(n.Data); err != nil {
 | 
      
         | 100 |  |  |                         return err
 | 
      
         | 101 |  |  |                 }
 | 
      
         | 102 |  |  |                 if n.Attr != nil {
 | 
      
         | 103 |  |  |                         var p, s string
 | 
      
         | 104 |  |  |                         for _, a := range n.Attr {
 | 
      
         | 105 |  |  |                                 switch a.Key {
 | 
      
         | 106 |  |  |                                 case "public":
 | 
      
         | 107 |  |  |                                         p = a.Val
 | 
      
         | 108 |  |  |                                 case "system":
 | 
      
         | 109 |  |  |                                         s = a.Val
 | 
      
         | 110 |  |  |                                 }
 | 
      
         | 111 |  |  |                         }
 | 
      
         | 112 |  |  |                         if p != "" {
 | 
      
         | 113 |  |  |                                 if _, err := w.WriteString(" PUBLIC "); err != nil {
 | 
      
         | 114 |  |  |                                         return err
 | 
      
         | 115 |  |  |                                 }
 | 
      
         | 116 |  |  |                                 if err := writeQuoted(w, p); err != nil {
 | 
      
         | 117 |  |  |                                         return err
 | 
      
         | 118 |  |  |                                 }
 | 
      
         | 119 |  |  |                                 if s != "" {
 | 
      
         | 120 |  |  |                                         if err := w.WriteByte(' '); err != nil {
 | 
      
         | 121 |  |  |                                                 return err
 | 
      
         | 122 |  |  |                                         }
 | 
      
         | 123 |  |  |                                         if err := writeQuoted(w, s); err != nil {
 | 
      
         | 124 |  |  |                                                 return err
 | 
      
         | 125 |  |  |                                         }
 | 
      
         | 126 |  |  |                                 }
 | 
      
         | 127 |  |  |                         } else if s != "" {
 | 
      
         | 128 |  |  |                                 if _, err := w.WriteString(" SYSTEM "); err != nil {
 | 
      
         | 129 |  |  |                                         return err
 | 
      
         | 130 |  |  |                                 }
 | 
      
         | 131 |  |  |                                 if err := writeQuoted(w, s); err != nil {
 | 
      
         | 132 |  |  |                                         return err
 | 
      
         | 133 |  |  |                                 }
 | 
      
         | 134 |  |  |                         }
 | 
      
         | 135 |  |  |                 }
 | 
      
         | 136 |  |  |                 return w.WriteByte('>')
 | 
      
         | 137 |  |  |         default:
 | 
      
         | 138 |  |  |                 return errors.New("html: unknown node type")
 | 
      
         | 139 |  |  |         }
 | 
      
         | 140 |  |  |  
 | 
      
         | 141 |  |  |         // Render the  opening tag.
 | 
      
         | 142 |  |  |         if err := w.WriteByte('<'); err != nil {
 | 
      
         | 143 |  |  |                 return err
 | 
      
         | 144 |  |  |         }
 | 
      
         | 145 |  |  |         if _, err := w.WriteString(n.Data); err != nil {
 | 
      
         | 146 |  |  |                 return err
 | 
      
         | 147 |  |  |         }
 | 
      
         | 148 |  |  |         for _, a := range n.Attr {
 | 
      
         | 149 |  |  |                 if err := w.WriteByte(' '); err != nil {
 | 
      
         | 150 |  |  |                         return err
 | 
      
         | 151 |  |  |                 }
 | 
      
         | 152 |  |  |                 if a.Namespace != "" {
 | 
      
         | 153 |  |  |                         if _, err := w.WriteString(a.Namespace); err != nil {
 | 
      
         | 154 |  |  |                                 return err
 | 
      
         | 155 |  |  |                         }
 | 
      
         | 156 |  |  |                         if err := w.WriteByte(':'); err != nil {
 | 
      
         | 157 |  |  |                                 return err
 | 
      
         | 158 |  |  |                         }
 | 
      
         | 159 |  |  |                 }
 | 
      
         | 160 |  |  |                 if _, err := w.WriteString(a.Key); err != nil {
 | 
      
         | 161 |  |  |                         return err
 | 
      
         | 162 |  |  |                 }
 | 
      
         | 163 |  |  |                 if _, err := w.WriteString(`="`); err != nil {
 | 
      
         | 164 |  |  |                         return err
 | 
      
         | 165 |  |  |                 }
 | 
      
         | 166 |  |  |                 if err := escape(w, a.Val); err != nil {
 | 
      
         | 167 |  |  |                         return err
 | 
      
         | 168 |  |  |                 }
 | 
      
         | 169 |  |  |                 if err := w.WriteByte('"'); err != nil {
 | 
      
         | 170 |  |  |                         return err
 | 
      
         | 171 |  |  |                 }
 | 
      
         | 172 |  |  |         }
 | 
      
         | 173 |  |  |         if voidElements[n.Data] {
 | 
      
         | 174 |  |  |                 if len(n.Child) != 0 {
 | 
      
         | 175 |  |  |                         return fmt.Errorf("html: void element <%s> has child nodes", n.Data)
 | 
      
         | 176 |  |  |                 }
 | 
      
         | 177 |  |  |                 _, err := w.WriteString("/>")
 | 
      
         | 178 |  |  |                 return err
 | 
      
         | 179 |  |  |         }
 | 
      
         | 180 |  |  |         if err := w.WriteByte('>'); err != nil {
 | 
      
         | 181 |  |  |                 return err
 | 
      
         | 182 |  |  |         }
 | 
      
         | 183 |  |  |  
 | 
      
         | 184 |  |  |         // Add initial newline where there is danger of a newline beging ignored.
 | 
      
         | 185 |  |  |         if len(n.Child) > 0 && n.Child[0].Type == TextNode && strings.HasPrefix(n.Child[0].Data, "\n") {
 | 
      
         | 186 |  |  |                 switch n.Data {
 | 
      
         | 187 |  |  |                 case "pre", "listing", "textarea":
 | 
      
         | 188 |  |  |                         if err := w.WriteByte('\n'); err != nil {
 | 
      
         | 189 |  |  |                                 return err
 | 
      
         | 190 |  |  |                         }
 | 
      
         | 191 |  |  |                 }
 | 
      
         | 192 |  |  |         }
 | 
      
         | 193 |  |  |  
 | 
      
         | 194 |  |  |         // Render any child nodes.
 | 
      
         | 195 |  |  |         switch n.Data {
 | 
      
         | 196 |  |  |         case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
 | 
      
         | 197 |  |  |                 for _, c := range n.Child {
 | 
      
         | 198 |  |  |                         if c.Type != TextNode {
 | 
      
         | 199 |  |  |                                 return fmt.Errorf("html: raw text element <%s> has non-text child node", n.Data)
 | 
      
         | 200 |  |  |                         }
 | 
      
         | 201 |  |  |                         if _, err := w.WriteString(c.Data); err != nil {
 | 
      
         | 202 |  |  |                                 return err
 | 
      
         | 203 |  |  |                         }
 | 
      
         | 204 |  |  |                 }
 | 
      
         | 205 |  |  |                 if n.Data == "plaintext" {
 | 
      
         | 206 |  |  |                         // Don't render anything else.  must be the
 | 
      
         | 207 |  |  |                         // last element in the file, with no closing tag.
 | 
      
         | 208 |  |  |                         return plaintextAbort
 | 
      
         | 209 |  |  |                 }
 | 
      
         | 210 |  |  |         case "textarea", "title":
 | 
      
         | 211 |  |  |                 for _, c := range n.Child {
 | 
      
         | 212 |  |  |                         if c.Type != TextNode {
 | 
      
         | 213 |  |  |                                 return fmt.Errorf("html: RCDATA element <%s> has non-text child node", n.Data)
 | 
      
         | 214 |  |  |                         }
 | 
      
         | 215 |  |  |                         if err := render1(w, c); err != nil {
 | 
      
         | 216 |  |  |                                 return err
 | 
      
         | 217 |  |  |                         }
 | 
      
         | 218 |  |  |                 }
 | 
      
         | 219 |  |  |         default:
 | 
      
         | 220 |  |  |                 for _, c := range n.Child {
 | 
      
         | 221 |  |  |                         if err := render1(w, c); err != nil {
 | 
      
         | 222 |  |  |                                 return err
 | 
      
         | 223 |  |  |                         }
 | 
      
         | 224 |  |  |                 }
 | 
      
         | 225 |  |  |         }
 | 
      
         | 226 |  |  |  
 | 
      
         | 227 |  |  |         // Render the  closing tag.
 | 
      
         | 228 |  |  |         if _, err := w.WriteString(""); err != nil {
 | 
      
         | 229 |  |  |                 return err
 | 
      
         | 230 |  |  |         }
 | 
      
         | 231 |  |  |         if _, err := w.WriteString(n.Data); err != nil {
 | 
      
         | 232 |  |  |                 return err
 | 
      
         | 233 |  |  |         }
 | 
      
         | 234 |  |  |         return w.WriteByte('>')
 | 
      
         | 235 |  |  | }
 | 
      
         | 236 |  |  |  
 | 
      
         | 237 |  |  | // writeQuoted writes s to w surrounded by quotes. Normally it will use double
 | 
      
         | 238 |  |  | // quotes, but if s contains a double quote, it will use single quotes.
 | 
      
         | 239 |  |  | // It is used for writing the identifiers in a doctype declaration.
 | 
      
         | 240 |  |  | // In valid HTML, they can't contain both types of quotes.
 | 
      
         | 241 |  |  | func writeQuoted(w writer, s string) error {
 | 
      
         | 242 |  |  |         var q byte = '"'
 | 
      
         | 243 |  |  |         if strings.Contains(s, `"`) {
 | 
      
         | 244 |  |  |                 q = '\''
 | 
      
         | 245 |  |  |         }
 | 
      
         | 246 |  |  |         if err := w.WriteByte(q); err != nil {
 | 
      
         | 247 |  |  |                 return err
 | 
      
         | 248 |  |  |         }
 | 
      
         | 249 |  |  |         if _, err := w.WriteString(s); err != nil {
 | 
      
         | 250 |  |  |                 return err
 | 
      
         | 251 |  |  |         }
 | 
      
         | 252 |  |  |         if err := w.WriteByte(q); err != nil {
 | 
      
         | 253 |  |  |                 return err
 | 
      
         | 254 |  |  |         }
 | 
      
         | 255 |  |  |         return nil
 | 
      
         | 256 |  |  | }
 | 
      
         | 257 |  |  |  
 | 
      
         | 258 |  |  | // Section 12.1.2, "Elements", gives this list of void elements. Void elements
 | 
      
         | 259 |  |  | // are those that can't have any contents.
 | 
      
         | 260 |  |  | var voidElements = map[string]bool{
 | 
      
         | 261 |  |  |         "area":    true,
 | 
      
         | 262 |  |  |         "base":    true,
 | 
      
         | 263 |  |  |         "br":      true,
 | 
      
         | 264 |  |  |         "col":     true,
 | 
      
         | 265 |  |  |         "command": true,
 | 
      
         | 266 |  |  |         "embed":   true,
 | 
      
         | 267 |  |  |         "hr":      true,
 | 
      
         | 268 |  |  |         "img":     true,
 | 
      
         | 269 |  |  |         "input":   true,
 | 
      
         | 270 |  |  |         "keygen":  true,
 | 
      
         | 271 |  |  |         "link":    true,
 | 
      
         | 272 |  |  |         "meta":    true,
 | 
      
         | 273 |  |  |         "param":   true,
 | 
      
         | 274 |  |  |         "source":  true,
 | 
      
         | 275 |  |  |         "track":   true,
 | 
      
         | 276 |  |  |         "wbr":     true,
 | 
      
         | 277 |  |  | }
 |