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
46namespace WebCore {
47
48using namespace HTMLNames;
49
50HTMLViewSourceDocument::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
61PassRefPtr<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
73void 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
100void 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
127void 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
136void 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
171void 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
178void HTMLViewSourceDocument::processCharacterToken(const String& source, HTMLToken&)
179{
180    addText(source, "");
181}
182
183PassRefPtr<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
199void 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
237void 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
267int 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
286PassRefPtr<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
297PassRefPtr<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