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 "TextViewSourceParser.h"
45 
46 namespace WebCore {
47 
48 using namespace HTMLNames;
49 
HTMLViewSourceDocument(Frame * frame,const KURL & url,const String & mimeType)50 HTMLViewSourceDocument::HTMLViewSourceDocument(Frame* frame, const KURL& url, const String& mimeType)
51     : HTMLDocument(frame, url)
52     , m_type(mimeType)
53 {
54     setUsesBeforeAfterRules(true);
55     setUsesViewSourceStyles(true);
56 
57     setCompatibilityMode(QuirksMode);
58     lockCompatibilityMode();
59 }
60 
createParser()61 PassRefPtr<DocumentParser> HTMLViewSourceDocument::createParser()
62 {
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 HTMLViewSourceParser::create(this);
69 
70     return TextViewSourceParser::create(this);
71 }
72 
createContainingTable()73 void HTMLViewSourceDocument::createContainingTable()
74 {
75     RefPtr<HTMLHtmlElement> html = HTMLHtmlElement::create(this);
76     parserAddChild(html);
77     html->attach();
78     RefPtr<HTMLBodyElement> body = HTMLBodyElement::create(this);
79     html->parserAddChild(body);
80     body->attach();
81 
82     // Create a line gutter div that can be used to make sure the gutter extends down the height of the whole
83     // document.
84     RefPtr<HTMLDivElement> div = HTMLDivElement::create(this);
85     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
86     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-gutter-backdrop"));
87     div->setAttributeMap(attrs.release());
88     body->parserAddChild(div);
89     div->attach();
90 
91     RefPtr<HTMLTableElement> table = HTMLTableElement::create(this);
92     body->parserAddChild(table);
93     table->attach();
94     m_tbody = HTMLTableSectionElement::create(tbodyTag, this);
95     table->parserAddChild(m_tbody);
96     m_tbody->attach();
97     m_current = m_tbody;
98 }
99 
addSource(const String & source,HTMLToken & token)100 void HTMLViewSourceDocument::addSource(const String& source, HTMLToken& token)
101 {
102     if (!m_current)
103         createContainingTable();
104 
105     switch (token.type()) {
106     case HTMLToken::Uninitialized:
107         ASSERT_NOT_REACHED();
108         break;
109     case HTMLToken::DOCTYPE:
110         processDoctypeToken(source, token);
111         break;
112     case HTMLToken::EndOfFile:
113         break;
114     case HTMLToken::StartTag:
115     case HTMLToken::EndTag:
116         processTagToken(source, token);
117         break;
118     case HTMLToken::Comment:
119         processCommentToken(source, token);
120         break;
121     case HTMLToken::Character:
122         processCharacterToken(source, token);
123         break;
124     }
125 }
126 
processDoctypeToken(const String & source,HTMLToken &)127 void HTMLViewSourceDocument::processDoctypeToken(const String& source, HTMLToken&)
128 {
129     if (!m_current)
130         createContainingTable();
131     m_current = addSpanWithClassName("webkit-html-doctype");
132     addText(source, "webkit-html-doctype");
133     m_current = m_td;
134 }
135 
processTagToken(const String & source,HTMLToken & token)136 void HTMLViewSourceDocument::processTagToken(const String& source, HTMLToken& token)
137 {
138     m_current = addSpanWithClassName("webkit-html-tag");
139 
140     AtomicString tagName(token.name().data(), token.name().size());
141 
142     unsigned index = 0;
143     HTMLToken::AttributeList::const_iterator iter = token.attributes().begin();
144     while (index < source.length()) {
145         if (iter == token.attributes().end()) {
146             // We want to show the remaining characters in the token.
147             index = addRange(source, index, source.length(), "");
148             ASSERT(index == source.length());
149             break;
150         }
151 
152         AtomicString name(iter->m_name.data(), iter->m_name.size());
153         String value(iter->m_value.data(), iter->m_value.size());
154 
155         index = addRange(source, index, iter->m_nameRange.m_start - token.startIndex(), "");
156         index = addRange(source, index, iter->m_nameRange.m_end - token.startIndex(), "webkit-html-attribute-name");
157 
158         if (tagName == baseTag && name == hrefAttr)
159             m_current = addBase(value);
160 
161         index = addRange(source, index, iter->m_valueRange.m_start - token.startIndex(), "");
162 
163         bool isLink = name == srcAttr || name == hrefAttr;
164         index = addRange(source, index, iter->m_valueRange.m_end - token.startIndex(), "webkit-html-attribute-value", isLink, tagName == aTag);
165 
166         ++iter;
167     }
168     m_current = m_td;
169 }
170 
processCommentToken(const String & source,HTMLToken &)171 void HTMLViewSourceDocument::processCommentToken(const String& source, HTMLToken&)
172 {
173     m_current = addSpanWithClassName("webkit-html-comment");
174     addText(source, "webkit-html-comment");
175     m_current = m_td;
176 }
177 
processCharacterToken(const String & source,HTMLToken &)178 void HTMLViewSourceDocument::processCharacterToken(const String& source, HTMLToken&)
179 {
180     addText(source, "");
181 }
182 
addSpanWithClassName(const AtomicString & className)183 PassRefPtr<Element> HTMLViewSourceDocument::addSpanWithClassName(const AtomicString& className)
184 {
185     if (m_current == m_tbody) {
186         addLine(className);
187         return m_current;
188     }
189 
190     RefPtr<HTMLElement> span = HTMLElement::create(spanTag, this);
191     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
192     attrs->addAttribute(Attribute::createMapped(classAttr, className));
193     span->setAttributeMap(attrs.release());
194     m_current->parserAddChild(span);
195     span->attach();
196     return span.release();
197 }
198 
addLine(const AtomicString & className)199 void HTMLViewSourceDocument::addLine(const AtomicString& className)
200 {
201     // Create a table row.
202     RefPtr<HTMLTableRowElement> trow = HTMLTableRowElement::create(this);
203     m_tbody->parserAddChild(trow);
204     trow->attach();
205 
206     // Create a cell that will hold the line number (it is generated in the stylesheet using counters).
207     RefPtr<HTMLTableCellElement> td = HTMLTableCellElement::create(tdTag, this);
208     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
209     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-number"));
210     td->setAttributeMap(attrs.release());
211     trow->parserAddChild(td);
212     td->attach();
213 
214     // Create a second cell for the line contents
215     td = HTMLTableCellElement::create(tdTag, this);
216     attrs = NamedNodeMap::create();
217     attrs->addAttribute(Attribute::createMapped(classAttr, "webkit-line-content"));
218     td->setAttributeMap(attrs.release());
219     trow->parserAddChild(td);
220     td->attach();
221     m_current = m_td = td;
222 
223 #ifdef DEBUG_LINE_NUMBERS
224     RefPtr<Text> lineNumberText = Text::create(this, String::number(parser()->lineNumber() + 1) + " ");
225     td->addChild(lineNumberText);
226     lineNumberText->attach();
227 #endif
228 
229     // Open up the needed spans.
230     if (!className.isEmpty()) {
231         if (className == "webkit-html-attribute-name" || className == "webkit-html-attribute-value")
232             m_current = addSpanWithClassName("webkit-html-tag");
233         m_current = addSpanWithClassName(className);
234     }
235 }
236 
addText(const String & text,const AtomicString & className)237 void HTMLViewSourceDocument::addText(const String& text, const AtomicString& className)
238 {
239     if (text.isEmpty())
240         return;
241 
242     // Add in the content, splitting on newlines.
243     Vector<String> lines;
244     text.split('\n', true, lines);
245     unsigned size = lines.size();
246     for (unsigned i = 0; i < size; i++) {
247         String substring = lines[i];
248         if (substring.isEmpty()) {
249             if (i == size - 1)
250                 break;
251             substring = " ";
252         }
253         if (m_current == m_tbody)
254             addLine(className);
255         RefPtr<Text> t = Text::create(this, substring);
256         m_current->parserAddChild(t);
257         t->attach();
258         if (i < size - 1)
259             m_current = m_tbody;
260     }
261 
262     // Set current to m_tbody if the last character was a newline.
263     if (text[text.length() - 1] == '\n')
264         m_current = m_tbody;
265 }
266 
addRange(const String & source,int start,int end,const String & className,bool isLink,bool isAnchor)267 int HTMLViewSourceDocument::addRange(const String& source, int start, int end, const String& className, bool isLink, bool isAnchor)
268 {
269     ASSERT(start <= end);
270     if (start == end)
271         return start;
272 
273     String text = source.substring(start, end - start);
274     if (!className.isEmpty()) {
275         if (isLink)
276             m_current = addLink(text, isAnchor);
277         else
278             m_current = addSpanWithClassName(className);
279     }
280     addText(text, className);
281     if (!className.isEmpty() && m_current != m_tbody)
282         m_current = static_cast<Element*>(m_current->parentNode());
283     return end;
284 }
285 
addBase(const AtomicString & href)286 PassRefPtr<Element> HTMLViewSourceDocument::addBase(const AtomicString& href)
287 {
288     RefPtr<HTMLBaseElement> base = HTMLBaseElement::create(baseTag, this);
289     RefPtr<NamedNodeMap> attributeMap = NamedNodeMap::create();
290     attributeMap->addAttribute(Attribute::createMapped(hrefAttr, href));
291     base->setAttributeMap(attributeMap.release());
292     m_current->parserAddChild(base);
293     base->attach();
294     return base.release();
295 }
296 
addLink(const AtomicString & url,bool isAnchor)297 PassRefPtr<Element> HTMLViewSourceDocument::addLink(const AtomicString& url, bool isAnchor)
298 {
299     if (m_current == m_tbody)
300         addLine("webkit-html-tag");
301 
302     // Now create a link for the attribute value instead of a span.
303     RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(this);
304     RefPtr<NamedNodeMap> attrs = NamedNodeMap::create();
305     const char* classValue;
306     if (isAnchor)
307         classValue = "webkit-html-attribute-value webkit-html-external-link";
308     else
309         classValue = "webkit-html-attribute-value webkit-html-resource-link";
310     attrs->addAttribute(Attribute::createMapped(classAttr, classValue));
311     attrs->addAttribute(Attribute::createMapped(targetAttr, "_blank"));
312     attrs->addAttribute(Attribute::createMapped(hrefAttr, url));
313     anchor->setAttributeMap(attrs.release());
314     m_current->parserAddChild(anchor);
315     anchor->attach();
316     return anchor.release();
317 }
318 
319 }
320