2010-09-08 Adam Barth <abarth@webkit.org>
[WebKit-https.git] / WebCore / html / HTMLViewSourceDocument.cpp
1 /*
2  * Copyright (C) 2006, 2008, 2009, 2010 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
20  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23  */
24
25 #include "config.h"
26 #include "HTMLViewSourceDocument.h"
27
28 #include "Attribute.h"
29 #include "DOMImplementation.h"
30 #include "HTMLAnchorElement.h"
31 #include "HTMLBaseElement.h"
32 #include "HTMLBodyElement.h"
33 #include "HTMLDivElement.h"
34 #include "HTMLHtmlElement.h"
35 #include "HTMLNames.h"
36 #include "HTMLTableCellElement.h"
37 #include "HTMLTableElement.h"
38 #include "HTMLTableRowElement.h"
39 #include "HTMLTableSectionElement.h"
40 #include "HTMLToken.h"
41 #include "HTMLViewSourceParser.h"
42 #include "SegmentedString.h"
43 #include "Text.h"
44 #include "TextDocument.h"
45
46 namespace WebCore {
47
48 using namespace HTMLNames;
49
50 HTMLViewSourceDocument::HTMLViewSourceDocument(Frame* frame, const KURL& url, const String& mimeType)
51     : HTMLDocument(frame, url)
52     , m_type(mimeType)
53 {
54     setUsesBeforeAfterRules(true);
55     setCompatibilityMode(QuirksMode);
56     lockCompatibilityMode();
57 }
58
59 PassRefPtr<DocumentParser> HTMLViewSourceDocument::createParser()
60 {
61     RefPtr<HTMLViewSourceParser> parser = HTMLViewSourceParser::create(this);
62     // Use HTMLDocumentParser if applicable, otherwise use TextDocumentParser.
63     if (m_type == "text/html" || m_type == "application/xhtml+xml" || m_type == "image/svg+xml" || DOMImplementation::isXMLMIMEType(m_type)
64 #if ENABLE(XHTMLMP)
65         || m_type == "application/vnd.wap.xhtml+xml"
66 #endif
67         )
68         return parser.release();
69
70     parser->forcePlaintext();
71     return parser.release();
72 }
73
74 void HTMLViewSourceDocument::createContainingTable()
75 {
76     RefPtr<HTMLHtmlElement> html = HTMLHtmlElement::create(this);
77     parserAddChild(html);
78     html->attach();
79     RefPtr<HTMLBodyElement> body = HTMLBodyElement::create(this);
80     html->parserAddChild(body);
81     body->attach();
82
83     // Create a line gutter div that can be used to make sure the gutter extends down the height of the whole
84     // document.
85     RefPtr<HTMLDivElement> div = HTMLDivElement::create(this);
86     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
87     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-gutter-backdrop"));
88     div->setAttributeMap(attrs.release());
89     body->parserAddChild(div);
90     div->attach();
91
92     RefPtr<HTMLTableElement> table = HTMLTableElement::create(this);
93     body->parserAddChild(table);
94     table->attach();
95     m_tbody = HTMLTableSectionElement::create(tbodyTag, this);
96     table->parserAddChild(m_tbody);
97     m_tbody->attach();
98     m_current = m_tbody;
99 }
100
101 void HTMLViewSourceDocument::addSource(const String& source, HTMLToken& token)
102 {
103     if (!m_current)
104         createContainingTable();
105
106     switch (token.type()) {
107     case HTMLToken::Uninitialized:
108         ASSERT_NOT_REACHED();
109         break;
110     case HTMLToken::DOCTYPE:
111         processDoctypeToken(source, token);
112         break;
113     case HTMLToken::EndOfFile:
114         break;
115     case HTMLToken::StartTag:
116     case HTMLToken::EndTag:
117         processTagToken(source, token);
118         break;
119     case HTMLToken::Comment:
120         processCommentToken(source, token);
121         break;
122     case HTMLToken::Character:
123         processCharacterToken(source, token);
124         break;
125     }
126 }
127
128 void HTMLViewSourceDocument::processDoctypeToken(const String& source, HTMLToken&)
129 {
130     if (!m_current)
131         createContainingTable();
132     m_current = addSpanWithClassName("webkit-html-doctype");
133     addText(source, "webkit-html-doctype");
134     m_current = m_td;
135 }
136
137 void HTMLViewSourceDocument::processTagToken(const String& source, HTMLToken& token)
138 {
139     m_current = addSpanWithClassName("webkit-html-tag");
140
141     AtomicString tagName(token.name().data(), token.name().size());
142
143     unsigned index = 0;
144     HTMLToken::AttributeList::const_iterator iter = token.attributes().begin();
145     while (index < source.length()) {
146         if (iter == token.attributes().end()) {
147             // We want to show the remaining characters in the token.
148             index = addRange(source, index, source.length(), "");
149             ASSERT(index == source.length());
150             break;
151         }
152
153         AtomicString name(iter->m_name.data(), iter->m_name.size());
154         String value(iter->m_value.data(), iter->m_value.size()); 
155
156         index = addRange(source, index, iter->m_nameRange.m_start - token.startIndex(), "");
157         index = addRange(source, index, iter->m_nameRange.m_end - token.startIndex(), "webkit-html-attribute-name");
158
159         if (tagName == baseTag && name == hrefAttr)
160             m_current = addBase(value);
161
162         index = addRange(source, index, iter->m_valueRange.m_start - token.startIndex(), "");
163
164         bool isLink = name == srcAttr || name == hrefAttr;
165         index = addRange(source, index, iter->m_valueRange.m_end - token.startIndex(), "webkit-html-attribute-value", isLink, tagName == aTag);
166
167         ++iter;
168     }
169     m_current = m_td;
170 }
171
172 void HTMLViewSourceDocument::processCommentToken(const String& source, HTMLToken&)
173 {
174     m_current = addSpanWithClassName("webkit-html-comment");
175     addText(source, "webkit-html-comment");
176     m_current = m_td;
177 }
178
179 void HTMLViewSourceDocument::processCharacterToken(const String& source, HTMLToken&)
180 {
181     addText(source, "");
182 }
183
184 PassRefPtr<Element> HTMLViewSourceDocument::addSpanWithClassName(const AtomicString& className)
185 {
186     if (m_current == m_tbody) {
187         addLine(className);
188         return m_current;
189     }
190
191     RefPtr<HTMLElement> span = HTMLElement::create(spanTag, this);
192     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
193     attrs->addAttribute(Attribute::createMapped(classAttr, className));
194     span->setAttributeMap(attrs.release());
195     m_current->parserAddChild(span);
196     span->attach();
197     return span.release();
198 }
199
200 void HTMLViewSourceDocument::addLine(const AtomicString& className)
201 {
202     // Create a table row.
203     RefPtr<HTMLTableRowElement> trow = HTMLTableRowElement::create(this);
204     m_tbody->parserAddChild(trow);
205     trow->attach();
206
207     // Create a cell that will hold the line number (it is generated in the stylesheet using counters).
208     RefPtr<HTMLTableCellElement> td = HTMLTableCellElement::create(tdTag, this);
209     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
210     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-number"));
211     td->setAttributeMap(attrs.release());
212     trow->parserAddChild(td);
213     td->attach();
214
215     // Create a second cell for the line contents
216     td = HTMLTableCellElement::create(tdTag, this);
217     attrs = NamedNodeMap::create();
218     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-content"));
219     td->setAttributeMap(attrs.release());
220     trow->parserAddChild(td);
221     td->attach();
222     m_current = m_td = td;
223
224 #ifdef DEBUG_LINE_NUMBERS
225     RefPtr<Text> lineNumberText = Text::create(this, String::number(parser()->lineNumber() + 1) + " ");
226     td->addChild(lineNumberText);
227     lineNumberText->attach();
228 #endif
229
230     // Open up the needed spans.
231     if (!className.isEmpty()) {
232         if (className == "webkit-html-attribute-name" || className == "webkit-html-attribute-value")
233             m_current = addSpanWithClassName("webkit-html-tag");
234         m_current = addSpanWithClassName(className);
235     }
236 }
237
238 void HTMLViewSourceDocument::addText(const String& text, const AtomicString& className)
239 {
240     if (text.isEmpty())
241         return;
242
243     // Add in the content, splitting on newlines.
244     Vector<String> lines;
245     text.split('\n', true, lines);
246     unsigned size = lines.size();
247     for (unsigned i = 0; i < size; i++) {
248         String substring = lines[i];
249         if (substring.isEmpty()) {
250             if (i == size - 1)
251                 break;
252             substring = " ";
253         }
254         if (m_current == m_tbody)
255             addLine(className);
256         RefPtr<Text> t = Text::create(this, substring);
257         m_current->parserAddChild(t);
258         t->attach();
259         if (i < size - 1)
260             m_current = m_tbody;
261     }
262
263     // Set current to m_tbody if the last character was a newline.
264     if (text[text.length() - 1] == '\n')
265         m_current = m_tbody;
266 }
267
268 int HTMLViewSourceDocument::addRange(const String& source, int start, int end, const String& className, bool isLink, bool isAnchor)
269 {
270     ASSERT(start <= end);
271     if (start == end)
272         return start;
273
274     String text = source.substring(start, end - start);
275     if (!className.isEmpty()) {
276         if (isLink)
277             m_current = addLink(text, isAnchor);
278         else
279             m_current = addSpanWithClassName(className);
280     }
281     addText(text, className);
282     if (!className.isEmpty() && m_current != m_tbody)
283         m_current = static_cast<Element*>(m_current->parent());
284     return end;
285 }
286
287 PassRefPtr<Element> HTMLViewSourceDocument::addBase(const AtomicString& href)
288 {
289     RefPtr<HTMLBaseElement> base = HTMLBaseElement::create(baseTag, this);
290     RefPtr<NamedNodeMap> attributeMap = NamedNodeMap::create();
291     attributeMap->addAttribute(Attribute::createMapped(hrefAttr, href));
292     base->setAttributeMap(attributeMap.release());
293     m_current->parserAddChild(base);
294     base->attach();
295     return base.release();
296 }
297
298 PassRefPtr<Element> HTMLViewSourceDocument::addLink(const AtomicString& url, bool isAnchor)
299 {
300     if (m_current == m_tbody)
301         addLine("webkit-html-tag");
302
303     // Now create a link for the attribute value instead of a span.
304     RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(this);
305     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
306     const char* classValue;
307     if (isAnchor)
308         classValue = "webkit-html-attribute-value webkit-html-external-link";
309     else
310         classValue = "webkit-html-attribute-value webkit-html-resource-link";
311     attrs->addAttribute(Attribute::createMapped(classAttr, classValue));
312     attrs->addAttribute(Attribute::createMapped(targetAttr, "_blank"));
313     attrs->addAttribute(Attribute::createMapped(hrefAttr, url));
314     anchor->setAttributeMap(attrs.release());
315     m_current->parserAddChild(anchor);
316     anchor->attach();
317     return anchor.release();
318 }
319
320 }