|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* vim: set ts=2 sw=2 et tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 /* |
|
8 * nsIContentSerializer implementation that can be used with an |
|
9 * nsIDocumentEncoder to convert an XHTML (not HTML!) DOM to an XHTML |
|
10 * string that could be parsed into more or less the original DOM. |
|
11 */ |
|
12 |
|
13 #include "nsXHTMLContentSerializer.h" |
|
14 |
|
15 #include "nsIDOMElement.h" |
|
16 #include "nsIContent.h" |
|
17 #include "nsIDocument.h" |
|
18 #include "nsNameSpaceManager.h" |
|
19 #include "nsString.h" |
|
20 #include "nsUnicharUtils.h" |
|
21 #include "nsXPIDLString.h" |
|
22 #include "nsIServiceManager.h" |
|
23 #include "nsIDocumentEncoder.h" |
|
24 #include "nsGkAtoms.h" |
|
25 #include "nsIURI.h" |
|
26 #include "nsNetUtil.h" |
|
27 #include "nsEscape.h" |
|
28 #include "nsITextToSubURI.h" |
|
29 #include "nsCRT.h" |
|
30 #include "nsIParserService.h" |
|
31 #include "nsContentUtils.h" |
|
32 #include "nsLWBrkCIID.h" |
|
33 #include "nsIScriptElement.h" |
|
34 #include "nsAttrName.h" |
|
35 #include "nsParserConstants.h" |
|
36 |
|
37 static const int32_t kLongLineLen = 128; |
|
38 |
|
39 #define kXMLNS "xmlns" |
|
40 |
|
41 nsresult NS_NewXHTMLContentSerializer(nsIContentSerializer** aSerializer) |
|
42 { |
|
43 nsXHTMLContentSerializer* it = new nsXHTMLContentSerializer(); |
|
44 if (!it) { |
|
45 return NS_ERROR_OUT_OF_MEMORY; |
|
46 } |
|
47 |
|
48 return CallQueryInterface(it, aSerializer); |
|
49 } |
|
50 |
|
51 nsXHTMLContentSerializer::nsXHTMLContentSerializer() |
|
52 : mIsHTMLSerializer(false) |
|
53 { |
|
54 } |
|
55 |
|
56 nsXHTMLContentSerializer::~nsXHTMLContentSerializer() |
|
57 { |
|
58 NS_ASSERTION(mOLStateStack.IsEmpty(), "Expected OL State stack to be empty"); |
|
59 } |
|
60 |
|
61 NS_IMETHODIMP |
|
62 nsXHTMLContentSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn, |
|
63 const char* aCharSet, bool aIsCopying, |
|
64 bool aRewriteEncodingDeclaration) |
|
65 { |
|
66 // The previous version of the HTML serializer did implicit wrapping |
|
67 // when there is no flags, so we keep wrapping in order to keep |
|
68 // compatibility with the existing calling code |
|
69 // XXXLJ perhaps should we remove this default settings later ? |
|
70 if (aFlags & nsIDocumentEncoder::OutputFormatted ) { |
|
71 aFlags = aFlags | nsIDocumentEncoder::OutputWrap; |
|
72 } |
|
73 |
|
74 nsresult rv; |
|
75 rv = nsXMLContentSerializer::Init(aFlags, aWrapColumn, aCharSet, aIsCopying, aRewriteEncodingDeclaration); |
|
76 NS_ENSURE_SUCCESS(rv, rv); |
|
77 |
|
78 mRewriteEncodingDeclaration = aRewriteEncodingDeclaration; |
|
79 mIsCopying = aIsCopying; |
|
80 mIsFirstChildOfOL = false; |
|
81 mInBody = 0; |
|
82 mDisableEntityEncoding = 0; |
|
83 mBodyOnly = (mFlags & nsIDocumentEncoder::OutputBodyOnly) ? true |
|
84 : false; |
|
85 |
|
86 // set up entity converter if we are going to need it |
|
87 if (mFlags & nsIDocumentEncoder::OutputEncodeW3CEntities) { |
|
88 mEntityConverter = do_CreateInstance(NS_ENTITYCONVERTER_CONTRACTID); |
|
89 } |
|
90 return NS_OK; |
|
91 } |
|
92 |
|
93 |
|
94 // See if the string has any lines longer than longLineLen: |
|
95 // if so, we presume formatting is wonky (e.g. the node has been edited) |
|
96 // and we'd better rewrap the whole text node. |
|
97 bool |
|
98 nsXHTMLContentSerializer::HasLongLines(const nsString& text, int32_t& aLastNewlineOffset) |
|
99 { |
|
100 uint32_t start=0; |
|
101 uint32_t theLen = text.Length(); |
|
102 bool rv = false; |
|
103 aLastNewlineOffset = kNotFound; |
|
104 for (start = 0; start < theLen; ) { |
|
105 int32_t eol = text.FindChar('\n', start); |
|
106 if (eol < 0) { |
|
107 eol = text.Length(); |
|
108 } |
|
109 else { |
|
110 aLastNewlineOffset = eol; |
|
111 } |
|
112 if (int32_t(eol - start) > kLongLineLen) |
|
113 rv = true; |
|
114 start = eol + 1; |
|
115 } |
|
116 return rv; |
|
117 } |
|
118 |
|
119 NS_IMETHODIMP |
|
120 nsXHTMLContentSerializer::AppendText(nsIContent* aText, |
|
121 int32_t aStartOffset, |
|
122 int32_t aEndOffset, |
|
123 nsAString& aStr) |
|
124 { |
|
125 NS_ENSURE_ARG(aText); |
|
126 |
|
127 nsAutoString data; |
|
128 nsresult rv; |
|
129 |
|
130 rv = AppendTextData(aText, aStartOffset, aEndOffset, data, true); |
|
131 if (NS_FAILED(rv)) |
|
132 return NS_ERROR_FAILURE; |
|
133 |
|
134 if (mPreLevel > 0 || mDoRaw) { |
|
135 AppendToStringConvertLF(data, aStr); |
|
136 } |
|
137 else if (mDoFormat) { |
|
138 AppendToStringFormatedWrapped(data, aStr); |
|
139 } |
|
140 else if (mDoWrap) { |
|
141 AppendToStringWrapped(data, aStr); |
|
142 } |
|
143 else { |
|
144 int32_t lastNewlineOffset = kNotFound; |
|
145 if (HasLongLines(data, lastNewlineOffset)) { |
|
146 // We have long lines, rewrap |
|
147 mDoWrap = true; |
|
148 AppendToStringWrapped(data, aStr); |
|
149 mDoWrap = false; |
|
150 } |
|
151 else { |
|
152 AppendToStringConvertLF(data, aStr); |
|
153 } |
|
154 } |
|
155 |
|
156 return NS_OK; |
|
157 } |
|
158 |
|
159 nsresult |
|
160 nsXHTMLContentSerializer::EscapeURI(nsIContent* aContent, const nsAString& aURI, nsAString& aEscapedURI) |
|
161 { |
|
162 // URL escape %xx cannot be used in JS. |
|
163 // No escaping if the scheme is 'javascript'. |
|
164 if (IsJavaScript(aContent, nsGkAtoms::href, kNameSpaceID_None, aURI)) { |
|
165 aEscapedURI = aURI; |
|
166 return NS_OK; |
|
167 } |
|
168 |
|
169 // nsITextToSubURI does charset convert plus uri escape |
|
170 // This is needed to convert to a document charset which is needed to support existing browsers. |
|
171 // But we eventually want to use UTF-8 instead of a document charset, then the code would be much simpler. |
|
172 // See HTML 4.01 spec, "Appendix B.2.1 Non-ASCII characters in URI attribute values" |
|
173 nsCOMPtr<nsITextToSubURI> textToSubURI; |
|
174 nsAutoString uri(aURI); // in order to use FindCharInSet() |
|
175 nsresult rv = NS_OK; |
|
176 |
|
177 if (!mCharset.IsEmpty() && !IsASCII(uri)) { |
|
178 textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); |
|
179 NS_ENSURE_SUCCESS(rv, rv); |
|
180 } |
|
181 |
|
182 int32_t start = 0; |
|
183 int32_t end; |
|
184 nsAutoString part; |
|
185 nsXPIDLCString escapedURI; |
|
186 aEscapedURI.Truncate(0); |
|
187 |
|
188 // Loop and escape parts by avoiding escaping reserved characters |
|
189 // (and '%', '#', as well as '[' and ']' for IPv6 address literals). |
|
190 while ((end = uri.FindCharInSet("%#;/?:@&=+$,[]", start)) != -1) { |
|
191 part = Substring(aURI, start, (end-start)); |
|
192 if (textToSubURI && !IsASCII(part)) { |
|
193 rv = textToSubURI->ConvertAndEscape(mCharset.get(), part.get(), getter_Copies(escapedURI)); |
|
194 NS_ENSURE_SUCCESS(rv, rv); |
|
195 } |
|
196 else { |
|
197 escapedURI.Adopt(nsEscape(NS_ConvertUTF16toUTF8(part).get(), url_Path)); |
|
198 } |
|
199 AppendASCIItoUTF16(escapedURI, aEscapedURI); |
|
200 |
|
201 // Append a reserved character without escaping. |
|
202 part = Substring(aURI, end, 1); |
|
203 aEscapedURI.Append(part); |
|
204 start = end + 1; |
|
205 } |
|
206 |
|
207 if (start < (int32_t) aURI.Length()) { |
|
208 // Escape the remaining part. |
|
209 part = Substring(aURI, start, aURI.Length()-start); |
|
210 if (textToSubURI) { |
|
211 rv = textToSubURI->ConvertAndEscape(mCharset.get(), part.get(), getter_Copies(escapedURI)); |
|
212 NS_ENSURE_SUCCESS(rv, rv); |
|
213 } |
|
214 else { |
|
215 escapedURI.Adopt(nsEscape(NS_ConvertUTF16toUTF8(part).get(), url_Path)); |
|
216 } |
|
217 AppendASCIItoUTF16(escapedURI, aEscapedURI); |
|
218 } |
|
219 |
|
220 return rv; |
|
221 } |
|
222 |
|
223 void |
|
224 nsXHTMLContentSerializer::SerializeAttributes(nsIContent* aContent, |
|
225 nsIContent *aOriginalElement, |
|
226 nsAString& aTagPrefix, |
|
227 const nsAString& aTagNamespaceURI, |
|
228 nsIAtom* aTagName, |
|
229 nsAString& aStr, |
|
230 uint32_t aSkipAttr, |
|
231 bool aAddNSAttr) |
|
232 { |
|
233 nsresult rv; |
|
234 uint32_t index, count; |
|
235 nsAutoString prefixStr, uriStr, valueStr; |
|
236 nsAutoString xmlnsStr; |
|
237 xmlnsStr.AssignLiteral(kXMLNS); |
|
238 |
|
239 int32_t contentNamespaceID = aContent->GetNameSpaceID(); |
|
240 |
|
241 // this method is not called by nsHTMLContentSerializer |
|
242 // so we don't have to check HTML element, just XHTML |
|
243 |
|
244 if (mIsCopying && kNameSpaceID_XHTML == contentNamespaceID) { |
|
245 |
|
246 // Need to keep track of OL and LI elements in order to get ordinal number |
|
247 // for the LI. |
|
248 if (aTagName == nsGkAtoms::ol) { |
|
249 // We are copying and current node is an OL; |
|
250 // Store its start attribute value in olState->startVal. |
|
251 nsAutoString start; |
|
252 int32_t startAttrVal = 0; |
|
253 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::start, start); |
|
254 if (!start.IsEmpty()) { |
|
255 nsresult rv = NS_OK; |
|
256 startAttrVal = start.ToInteger(&rv); |
|
257 //If OL has "start" attribute, first LI element has to start with that value |
|
258 //Therefore subtracting 1 as all the LI elements are incrementing it before using it; |
|
259 //In failure of ToInteger(), default StartAttrValue to 0. |
|
260 if (NS_SUCCEEDED(rv)) |
|
261 --startAttrVal; |
|
262 else |
|
263 startAttrVal = 0; |
|
264 } |
|
265 olState state (startAttrVal, true); |
|
266 mOLStateStack.AppendElement(state); |
|
267 } |
|
268 else if (aTagName == nsGkAtoms::li) { |
|
269 mIsFirstChildOfOL = IsFirstChildOfOL(aOriginalElement); |
|
270 if (mIsFirstChildOfOL) { |
|
271 // If OL is parent of this LI, serialize attributes in different manner. |
|
272 SerializeLIValueAttribute(aContent, aStr); |
|
273 } |
|
274 } |
|
275 } |
|
276 |
|
277 // If we had to add a new namespace declaration, serialize |
|
278 // and push it on the namespace stack |
|
279 if (aAddNSAttr) { |
|
280 if (aTagPrefix.IsEmpty()) { |
|
281 // Serialize default namespace decl |
|
282 SerializeAttr(EmptyString(), xmlnsStr, aTagNamespaceURI, aStr, true); |
|
283 } else { |
|
284 // Serialize namespace decl |
|
285 SerializeAttr(xmlnsStr, aTagPrefix, aTagNamespaceURI, aStr, true); |
|
286 } |
|
287 PushNameSpaceDecl(aTagPrefix, aTagNamespaceURI, aOriginalElement); |
|
288 } |
|
289 |
|
290 NS_NAMED_LITERAL_STRING(_mozStr, "_moz"); |
|
291 |
|
292 count = aContent->GetAttrCount(); |
|
293 |
|
294 // Now serialize each of the attributes |
|
295 // XXX Unfortunately we need a namespace manager to get |
|
296 // attribute URIs. |
|
297 for (index = 0; index < count; index++) { |
|
298 |
|
299 if (aSkipAttr == index) { |
|
300 continue; |
|
301 } |
|
302 |
|
303 const nsAttrName* name = aContent->GetAttrNameAt(index); |
|
304 int32_t namespaceID = name->NamespaceID(); |
|
305 nsIAtom* attrName = name->LocalName(); |
|
306 nsIAtom* attrPrefix = name->GetPrefix(); |
|
307 |
|
308 // Filter out any attribute starting with [-|_]moz |
|
309 nsDependentAtomString attrNameStr(attrName); |
|
310 if (StringBeginsWith(attrNameStr, NS_LITERAL_STRING("_moz")) || |
|
311 StringBeginsWith(attrNameStr, NS_LITERAL_STRING("-moz"))) { |
|
312 continue; |
|
313 } |
|
314 |
|
315 if (attrPrefix) { |
|
316 attrPrefix->ToString(prefixStr); |
|
317 } |
|
318 else { |
|
319 prefixStr.Truncate(); |
|
320 } |
|
321 |
|
322 bool addNSAttr = false; |
|
323 if (kNameSpaceID_XMLNS != namespaceID) { |
|
324 nsContentUtils::NameSpaceManager()->GetNameSpaceURI(namespaceID, uriStr); |
|
325 addNSAttr = ConfirmPrefix(prefixStr, uriStr, aOriginalElement, true); |
|
326 } |
|
327 |
|
328 aContent->GetAttr(namespaceID, attrName, valueStr); |
|
329 |
|
330 nsDependentAtomString nameStr(attrName); |
|
331 bool isJS = false; |
|
332 |
|
333 if (kNameSpaceID_XHTML == contentNamespaceID) { |
|
334 // |
|
335 // Filter out special case of <br type="_moz"> or <br _moz*>, |
|
336 // used by the editor. Bug 16988. Yuck. |
|
337 // |
|
338 if (namespaceID == kNameSpaceID_None && aTagName == nsGkAtoms::br && attrName == nsGkAtoms::type |
|
339 && StringBeginsWith(valueStr, _mozStr)) { |
|
340 continue; |
|
341 } |
|
342 |
|
343 if (mIsCopying && mIsFirstChildOfOL && (aTagName == nsGkAtoms::li) |
|
344 && (attrName == nsGkAtoms::value)) { |
|
345 // This is handled separately in SerializeLIValueAttribute() |
|
346 continue; |
|
347 } |
|
348 |
|
349 isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr); |
|
350 |
|
351 if (namespaceID == kNameSpaceID_None && |
|
352 ((attrName == nsGkAtoms::href) || |
|
353 (attrName == nsGkAtoms::src))) { |
|
354 // Make all links absolute when converting only the selection: |
|
355 if (mFlags & nsIDocumentEncoder::OutputAbsoluteLinks) { |
|
356 // Would be nice to handle OBJECT and APPLET tags, |
|
357 // but that gets more complicated since we have to |
|
358 // search the tag list for CODEBASE as well. |
|
359 // For now, just leave them relative. |
|
360 nsCOMPtr<nsIURI> uri = aContent->GetBaseURI(); |
|
361 if (uri) { |
|
362 nsAutoString absURI; |
|
363 rv = NS_MakeAbsoluteURI(absURI, valueStr, uri); |
|
364 if (NS_SUCCEEDED(rv)) { |
|
365 valueStr = absURI; |
|
366 } |
|
367 } |
|
368 } |
|
369 // Need to escape URI. |
|
370 nsAutoString tempURI(valueStr); |
|
371 if (!isJS && NS_FAILED(EscapeURI(aContent, tempURI, valueStr))) |
|
372 valueStr = tempURI; |
|
373 } |
|
374 |
|
375 if (mRewriteEncodingDeclaration && aTagName == nsGkAtoms::meta && |
|
376 attrName == nsGkAtoms::content) { |
|
377 // If we're serializing a <meta http-equiv="content-type">, |
|
378 // use the proper value, rather than what's in the document. |
|
379 nsAutoString header; |
|
380 aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); |
|
381 if (header.LowerCaseEqualsLiteral("content-type")) { |
|
382 valueStr = NS_LITERAL_STRING("text/html; charset=") + |
|
383 NS_ConvertASCIItoUTF16(mCharset); |
|
384 } |
|
385 } |
|
386 |
|
387 // Expand shorthand attribute. |
|
388 if (namespaceID == kNameSpaceID_None && IsShorthandAttr(attrName, aTagName) && valueStr.IsEmpty()) { |
|
389 valueStr = nameStr; |
|
390 } |
|
391 } |
|
392 else { |
|
393 isJS = IsJavaScript(aContent, attrName, namespaceID, valueStr); |
|
394 } |
|
395 |
|
396 SerializeAttr(prefixStr, nameStr, valueStr, aStr, !isJS); |
|
397 |
|
398 if (addNSAttr) { |
|
399 NS_ASSERTION(!prefixStr.IsEmpty(), |
|
400 "Namespaced attributes must have a prefix"); |
|
401 SerializeAttr(xmlnsStr, prefixStr, uriStr, aStr, true); |
|
402 PushNameSpaceDecl(prefixStr, uriStr, aOriginalElement); |
|
403 } |
|
404 } |
|
405 } |
|
406 |
|
407 |
|
408 void |
|
409 nsXHTMLContentSerializer::AppendEndOfElementStart(nsIContent *aOriginalElement, |
|
410 nsIAtom * aName, |
|
411 int32_t aNamespaceID, |
|
412 nsAString& aStr) |
|
413 { |
|
414 // this method is not called by nsHTMLContentSerializer |
|
415 // so we don't have to check HTML element, just XHTML |
|
416 NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !"); |
|
417 |
|
418 if (kNameSpaceID_XHTML != aNamespaceID) { |
|
419 nsXMLContentSerializer::AppendEndOfElementStart(aOriginalElement, aName, |
|
420 aNamespaceID, aStr); |
|
421 return; |
|
422 } |
|
423 |
|
424 nsIContent* content = aOriginalElement; |
|
425 |
|
426 // for non empty elements, even if they are not a container, we always |
|
427 // serialize their content, because the XHTML element could contain non XHTML |
|
428 // nodes useful in some context, like in an XSLT stylesheet |
|
429 if (HasNoChildren(content)) { |
|
430 |
|
431 nsIParserService* parserService = nsContentUtils::GetParserService(); |
|
432 |
|
433 if (parserService) { |
|
434 bool isContainer; |
|
435 parserService-> |
|
436 IsContainer(parserService->HTMLCaseSensitiveAtomTagToId(aName), |
|
437 isContainer); |
|
438 if (!isContainer) { |
|
439 // for backward compatibility with HTML 4 user agents |
|
440 // only non-container HTML elements can be closed immediatly, |
|
441 // and a space is added before /> |
|
442 AppendToString(NS_LITERAL_STRING(" />"), aStr); |
|
443 return; |
|
444 } |
|
445 } |
|
446 } |
|
447 AppendToString(kGreaterThan, aStr); |
|
448 } |
|
449 |
|
450 void |
|
451 nsXHTMLContentSerializer::AfterElementStart(nsIContent * aContent, |
|
452 nsIContent *aOriginalElement, |
|
453 nsAString& aStr) |
|
454 { |
|
455 nsIAtom *name = aContent->Tag(); |
|
456 if (aContent->GetNameSpaceID() == kNameSpaceID_XHTML && |
|
457 mRewriteEncodingDeclaration && |
|
458 name == nsGkAtoms::head) { |
|
459 |
|
460 // Check if there already are any content-type meta children. |
|
461 // If there are, they will be modified to use the correct charset. |
|
462 // If there aren't, we'll insert one here. |
|
463 bool hasMeta = false; |
|
464 for (nsIContent* child = aContent->GetFirstChild(); |
|
465 child; |
|
466 child = child->GetNextSibling()) { |
|
467 if (child->IsHTML(nsGkAtoms::meta) && |
|
468 child->HasAttr(kNameSpaceID_None, nsGkAtoms::content)) { |
|
469 nsAutoString header; |
|
470 child->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, header); |
|
471 |
|
472 if (header.LowerCaseEqualsLiteral("content-type")) { |
|
473 hasMeta = true; |
|
474 break; |
|
475 } |
|
476 } |
|
477 } |
|
478 |
|
479 if (!hasMeta) { |
|
480 AppendNewLineToString(aStr); |
|
481 if (mDoFormat) { |
|
482 AppendIndentation(aStr); |
|
483 } |
|
484 AppendToString(NS_LITERAL_STRING("<meta http-equiv=\"content-type\""), |
|
485 aStr); |
|
486 AppendToString(NS_LITERAL_STRING(" content=\"text/html; charset="), aStr); |
|
487 AppendToString(NS_ConvertASCIItoUTF16(mCharset), aStr); |
|
488 if (mIsHTMLSerializer) |
|
489 AppendToString(NS_LITERAL_STRING("\">"), aStr); |
|
490 else |
|
491 AppendToString(NS_LITERAL_STRING("\" />"), aStr); |
|
492 } |
|
493 } |
|
494 } |
|
495 |
|
496 void |
|
497 nsXHTMLContentSerializer::AfterElementEnd(nsIContent * aContent, |
|
498 nsAString& aStr) |
|
499 { |
|
500 NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !"); |
|
501 |
|
502 int32_t namespaceID = aContent->GetNameSpaceID(); |
|
503 nsIAtom *name = aContent->Tag(); |
|
504 |
|
505 // this method is not called by nsHTMLContentSerializer |
|
506 // so we don't have to check HTML element, just XHTML |
|
507 if (kNameSpaceID_XHTML == namespaceID && name == nsGkAtoms::body) { |
|
508 --mInBody; |
|
509 } |
|
510 } |
|
511 |
|
512 |
|
513 NS_IMETHODIMP |
|
514 nsXHTMLContentSerializer::AppendDocumentStart(nsIDocument *aDocument, |
|
515 nsAString& aStr) |
|
516 { |
|
517 if (!mBodyOnly) |
|
518 return nsXMLContentSerializer::AppendDocumentStart(aDocument, aStr); |
|
519 |
|
520 return NS_OK; |
|
521 } |
|
522 |
|
523 bool |
|
524 nsXHTMLContentSerializer::CheckElementStart(nsIContent * aContent, |
|
525 bool & aForceFormat, |
|
526 nsAString& aStr) |
|
527 { |
|
528 // The _moz_dirty attribute is emitted by the editor to |
|
529 // indicate that this element should be pretty printed |
|
530 // even if we're not in pretty printing mode |
|
531 aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && |
|
532 aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty); |
|
533 |
|
534 nsIAtom *name = aContent->Tag(); |
|
535 int32_t namespaceID = aContent->GetNameSpaceID(); |
|
536 |
|
537 if (namespaceID == kNameSpaceID_XHTML) { |
|
538 if (name == nsGkAtoms::br && mPreLevel > 0 && |
|
539 (mFlags & nsIDocumentEncoder::OutputNoFormattingInPre)) { |
|
540 AppendNewLineToString(aStr); |
|
541 return false; |
|
542 } |
|
543 |
|
544 if (name == nsGkAtoms::body) { |
|
545 ++mInBody; |
|
546 } |
|
547 } |
|
548 return true; |
|
549 } |
|
550 |
|
551 bool |
|
552 nsXHTMLContentSerializer::CheckElementEnd(nsIContent * aContent, |
|
553 bool & aForceFormat, |
|
554 nsAString& aStr) |
|
555 { |
|
556 NS_ASSERTION(!mIsHTMLSerializer, "nsHTMLContentSerializer shouldn't call this method !"); |
|
557 |
|
558 aForceFormat = !(mFlags & nsIDocumentEncoder::OutputIgnoreMozDirty) && |
|
559 aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozdirty); |
|
560 |
|
561 nsIAtom *name = aContent->Tag(); |
|
562 int32_t namespaceID = aContent->GetNameSpaceID(); |
|
563 |
|
564 // this method is not called by nsHTMLContentSerializer |
|
565 // so we don't have to check HTML element, just XHTML |
|
566 if (namespaceID == kNameSpaceID_XHTML) { |
|
567 if (mIsCopying && name == nsGkAtoms::ol) { |
|
568 NS_ASSERTION((!mOLStateStack.IsEmpty()), "Cannot have an empty OL Stack"); |
|
569 /* Though at this point we must always have an state to be deleted as all |
|
570 the OL opening tags are supposed to push an olState object to the stack*/ |
|
571 if (!mOLStateStack.IsEmpty()) { |
|
572 mOLStateStack.RemoveElementAt(mOLStateStack.Length() -1); |
|
573 } |
|
574 } |
|
575 |
|
576 if (HasNoChildren(aContent)) { |
|
577 nsIParserService* parserService = nsContentUtils::GetParserService(); |
|
578 |
|
579 if (parserService) { |
|
580 bool isContainer; |
|
581 |
|
582 parserService-> |
|
583 IsContainer(parserService->HTMLCaseSensitiveAtomTagToId(name), |
|
584 isContainer); |
|
585 if (!isContainer) { |
|
586 // non-container HTML elements are already closed, |
|
587 // see AppendEndOfElementStart |
|
588 return false; |
|
589 } |
|
590 } |
|
591 } |
|
592 // for backward compatibility with old HTML user agents, |
|
593 // empty elements should have an ending tag, so we mustn't call |
|
594 // nsXMLContentSerializer::CheckElementEnd |
|
595 return true; |
|
596 } |
|
597 |
|
598 bool dummyFormat; |
|
599 return nsXMLContentSerializer::CheckElementEnd(aContent, dummyFormat, aStr); |
|
600 } |
|
601 |
|
602 void |
|
603 nsXHTMLContentSerializer::AppendAndTranslateEntities(const nsAString& aStr, |
|
604 nsAString& aOutputStr) |
|
605 { |
|
606 if (mBodyOnly && !mInBody) { |
|
607 return; |
|
608 } |
|
609 |
|
610 if (mDisableEntityEncoding) { |
|
611 aOutputStr.Append(aStr); |
|
612 return; |
|
613 } |
|
614 |
|
615 nsXMLContentSerializer::AppendAndTranslateEntities(aStr, aOutputStr); |
|
616 } |
|
617 |
|
618 bool |
|
619 nsXHTMLContentSerializer::IsShorthandAttr(const nsIAtom* aAttrName, |
|
620 const nsIAtom* aElementName) |
|
621 { |
|
622 // checked |
|
623 if ((aAttrName == nsGkAtoms::checked) && |
|
624 (aElementName == nsGkAtoms::input)) { |
|
625 return true; |
|
626 } |
|
627 |
|
628 // compact |
|
629 if ((aAttrName == nsGkAtoms::compact) && |
|
630 (aElementName == nsGkAtoms::dir || |
|
631 aElementName == nsGkAtoms::dl || |
|
632 aElementName == nsGkAtoms::menu || |
|
633 aElementName == nsGkAtoms::ol || |
|
634 aElementName == nsGkAtoms::ul)) { |
|
635 return true; |
|
636 } |
|
637 |
|
638 // declare |
|
639 if ((aAttrName == nsGkAtoms::declare) && |
|
640 (aElementName == nsGkAtoms::object)) { |
|
641 return true; |
|
642 } |
|
643 |
|
644 // defer |
|
645 if ((aAttrName == nsGkAtoms::defer) && |
|
646 (aElementName == nsGkAtoms::script)) { |
|
647 return true; |
|
648 } |
|
649 |
|
650 // disabled |
|
651 if ((aAttrName == nsGkAtoms::disabled) && |
|
652 (aElementName == nsGkAtoms::button || |
|
653 aElementName == nsGkAtoms::input || |
|
654 aElementName == nsGkAtoms::optgroup || |
|
655 aElementName == nsGkAtoms::option || |
|
656 aElementName == nsGkAtoms::select || |
|
657 aElementName == nsGkAtoms::textarea)) { |
|
658 return true; |
|
659 } |
|
660 |
|
661 // ismap |
|
662 if ((aAttrName == nsGkAtoms::ismap) && |
|
663 (aElementName == nsGkAtoms::img || |
|
664 aElementName == nsGkAtoms::input)) { |
|
665 return true; |
|
666 } |
|
667 |
|
668 // multiple |
|
669 if ((aAttrName == nsGkAtoms::multiple) && |
|
670 (aElementName == nsGkAtoms::select)) { |
|
671 return true; |
|
672 } |
|
673 |
|
674 // noresize |
|
675 if ((aAttrName == nsGkAtoms::noresize) && |
|
676 (aElementName == nsGkAtoms::frame)) { |
|
677 return true; |
|
678 } |
|
679 |
|
680 // noshade |
|
681 if ((aAttrName == nsGkAtoms::noshade) && |
|
682 (aElementName == nsGkAtoms::hr)) { |
|
683 return true; |
|
684 } |
|
685 |
|
686 // nowrap |
|
687 if ((aAttrName == nsGkAtoms::nowrap) && |
|
688 (aElementName == nsGkAtoms::td || |
|
689 aElementName == nsGkAtoms::th)) { |
|
690 return true; |
|
691 } |
|
692 |
|
693 // readonly |
|
694 if ((aAttrName == nsGkAtoms::readonly) && |
|
695 (aElementName == nsGkAtoms::input || |
|
696 aElementName == nsGkAtoms::textarea)) { |
|
697 return true; |
|
698 } |
|
699 |
|
700 // selected |
|
701 if ((aAttrName == nsGkAtoms::selected) && |
|
702 (aElementName == nsGkAtoms::option)) { |
|
703 return true; |
|
704 } |
|
705 |
|
706 // autoplay and controls |
|
707 if ((aElementName == nsGkAtoms::video || aElementName == nsGkAtoms::audio) && |
|
708 (aAttrName == nsGkAtoms::autoplay || aAttrName == nsGkAtoms::muted || |
|
709 aAttrName == nsGkAtoms::controls)) { |
|
710 return true; |
|
711 } |
|
712 |
|
713 return false; |
|
714 } |
|
715 |
|
716 bool |
|
717 nsXHTMLContentSerializer::LineBreakBeforeOpen(int32_t aNamespaceID, nsIAtom* aName) |
|
718 { |
|
719 |
|
720 if (aNamespaceID != kNameSpaceID_XHTML) { |
|
721 return mAddSpace; |
|
722 } |
|
723 |
|
724 if (aName == nsGkAtoms::title || |
|
725 aName == nsGkAtoms::meta || |
|
726 aName == nsGkAtoms::link || |
|
727 aName == nsGkAtoms::style || |
|
728 aName == nsGkAtoms::select || |
|
729 aName == nsGkAtoms::option || |
|
730 aName == nsGkAtoms::script || |
|
731 aName == nsGkAtoms::html) { |
|
732 return true; |
|
733 } |
|
734 else { |
|
735 nsIParserService* parserService = nsContentUtils::GetParserService(); |
|
736 |
|
737 if (parserService) { |
|
738 bool res; |
|
739 parserService-> |
|
740 IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res); |
|
741 return res; |
|
742 } |
|
743 } |
|
744 |
|
745 return mAddSpace; |
|
746 } |
|
747 |
|
748 bool |
|
749 nsXHTMLContentSerializer::LineBreakAfterOpen(int32_t aNamespaceID, nsIAtom* aName) |
|
750 { |
|
751 |
|
752 if (aNamespaceID != kNameSpaceID_XHTML) { |
|
753 return false; |
|
754 } |
|
755 |
|
756 if ((aName == nsGkAtoms::html) || |
|
757 (aName == nsGkAtoms::head) || |
|
758 (aName == nsGkAtoms::body) || |
|
759 (aName == nsGkAtoms::ul) || |
|
760 (aName == nsGkAtoms::ol) || |
|
761 (aName == nsGkAtoms::dl) || |
|
762 (aName == nsGkAtoms::table) || |
|
763 (aName == nsGkAtoms::tbody) || |
|
764 (aName == nsGkAtoms::tr) || |
|
765 (aName == nsGkAtoms::br) || |
|
766 (aName == nsGkAtoms::meta) || |
|
767 (aName == nsGkAtoms::link) || |
|
768 (aName == nsGkAtoms::script) || |
|
769 (aName == nsGkAtoms::select) || |
|
770 (aName == nsGkAtoms::map) || |
|
771 (aName == nsGkAtoms::area) || |
|
772 (aName == nsGkAtoms::style)) { |
|
773 return true; |
|
774 } |
|
775 |
|
776 return false; |
|
777 } |
|
778 |
|
779 bool |
|
780 nsXHTMLContentSerializer::LineBreakBeforeClose(int32_t aNamespaceID, nsIAtom* aName) |
|
781 { |
|
782 |
|
783 if (aNamespaceID != kNameSpaceID_XHTML) { |
|
784 return false; |
|
785 } |
|
786 |
|
787 if ((aName == nsGkAtoms::html) || |
|
788 (aName == nsGkAtoms::head) || |
|
789 (aName == nsGkAtoms::body) || |
|
790 (aName == nsGkAtoms::ul) || |
|
791 (aName == nsGkAtoms::ol) || |
|
792 (aName == nsGkAtoms::dl) || |
|
793 (aName == nsGkAtoms::select) || |
|
794 (aName == nsGkAtoms::table) || |
|
795 (aName == nsGkAtoms::tbody)) { |
|
796 return true; |
|
797 } |
|
798 return false; |
|
799 } |
|
800 |
|
801 bool |
|
802 nsXHTMLContentSerializer::LineBreakAfterClose(int32_t aNamespaceID, nsIAtom* aName) |
|
803 { |
|
804 |
|
805 if (aNamespaceID != kNameSpaceID_XHTML) { |
|
806 return false; |
|
807 } |
|
808 |
|
809 if ((aName == nsGkAtoms::html) || |
|
810 (aName == nsGkAtoms::head) || |
|
811 (aName == nsGkAtoms::body) || |
|
812 (aName == nsGkAtoms::tr) || |
|
813 (aName == nsGkAtoms::th) || |
|
814 (aName == nsGkAtoms::td) || |
|
815 (aName == nsGkAtoms::pre) || |
|
816 (aName == nsGkAtoms::title) || |
|
817 (aName == nsGkAtoms::li) || |
|
818 (aName == nsGkAtoms::dt) || |
|
819 (aName == nsGkAtoms::dd) || |
|
820 (aName == nsGkAtoms::blockquote) || |
|
821 (aName == nsGkAtoms::select) || |
|
822 (aName == nsGkAtoms::option) || |
|
823 (aName == nsGkAtoms::p) || |
|
824 (aName == nsGkAtoms::map) || |
|
825 (aName == nsGkAtoms::div)) { |
|
826 return true; |
|
827 } |
|
828 else { |
|
829 nsIParserService* parserService = nsContentUtils::GetParserService(); |
|
830 |
|
831 if (parserService) { |
|
832 bool res; |
|
833 parserService-> |
|
834 IsBlock(parserService->HTMLCaseSensitiveAtomTagToId(aName), res); |
|
835 return res; |
|
836 } |
|
837 } |
|
838 |
|
839 return false; |
|
840 } |
|
841 |
|
842 |
|
843 void |
|
844 nsXHTMLContentSerializer::MaybeEnterInPreContent(nsIContent* aNode) |
|
845 { |
|
846 |
|
847 if (aNode->GetNameSpaceID() != kNameSpaceID_XHTML) { |
|
848 return; |
|
849 } |
|
850 |
|
851 nsIAtom *name = aNode->Tag(); |
|
852 |
|
853 if (name == nsGkAtoms::pre || |
|
854 name == nsGkAtoms::script || |
|
855 name == nsGkAtoms::style || |
|
856 name == nsGkAtoms::noscript || |
|
857 name == nsGkAtoms::noframes |
|
858 ) { |
|
859 mPreLevel++; |
|
860 } |
|
861 } |
|
862 |
|
863 void |
|
864 nsXHTMLContentSerializer::MaybeLeaveFromPreContent(nsIContent* aNode) |
|
865 { |
|
866 if (aNode->GetNameSpaceID() != kNameSpaceID_XHTML) { |
|
867 return; |
|
868 } |
|
869 |
|
870 nsIAtom *name = aNode->Tag(); |
|
871 if (name == nsGkAtoms::pre || |
|
872 name == nsGkAtoms::script || |
|
873 name == nsGkAtoms::style || |
|
874 name == nsGkAtoms::noscript || |
|
875 name == nsGkAtoms::noframes |
|
876 ) { |
|
877 --mPreLevel; |
|
878 } |
|
879 } |
|
880 |
|
881 void |
|
882 nsXHTMLContentSerializer::SerializeLIValueAttribute(nsIContent* aElement, |
|
883 nsAString& aStr) |
|
884 { |
|
885 // We are copying and we are at the "first" LI node of OL in selected range. |
|
886 // It may not be the first LI child of OL but it's first in the selected range. |
|
887 // Note that we get into this condition only once per a OL. |
|
888 bool found = false; |
|
889 nsCOMPtr<nsIDOMNode> currNode = do_QueryInterface(aElement); |
|
890 nsAutoString valueStr; |
|
891 |
|
892 olState state (0, false); |
|
893 |
|
894 if (!mOLStateStack.IsEmpty()) { |
|
895 state = mOLStateStack[mOLStateStack.Length()-1]; |
|
896 // isFirstListItem should be true only before the serialization of the |
|
897 // first item in the list. |
|
898 state.isFirstListItem = false; |
|
899 mOLStateStack[mOLStateStack.Length()-1] = state; |
|
900 } |
|
901 |
|
902 int32_t startVal = state.startVal; |
|
903 int32_t offset = 0; |
|
904 |
|
905 // Traverse previous siblings until we find one with "value" attribute. |
|
906 // offset keeps track of how many previous siblings we had tocurrNode traverse. |
|
907 while (currNode && !found) { |
|
908 nsCOMPtr<nsIDOMElement> currElement = do_QueryInterface(currNode); |
|
909 // currElement may be null if it were a text node. |
|
910 if (currElement) { |
|
911 nsAutoString tagName; |
|
912 currElement->GetTagName(tagName); |
|
913 if (tagName.LowerCaseEqualsLiteral("li")) { |
|
914 currElement->GetAttribute(NS_LITERAL_STRING("value"), valueStr); |
|
915 if (valueStr.IsEmpty()) |
|
916 offset++; |
|
917 else { |
|
918 found = true; |
|
919 nsresult rv = NS_OK; |
|
920 startVal = valueStr.ToInteger(&rv); |
|
921 } |
|
922 } |
|
923 } |
|
924 nsCOMPtr<nsIDOMNode> tmp; |
|
925 currNode->GetPreviousSibling(getter_AddRefs(tmp)); |
|
926 currNode.swap(tmp); |
|
927 } |
|
928 // If LI was not having "value", Set the "value" attribute for it. |
|
929 // Note that We are at the first LI in the selected range of OL. |
|
930 if (offset == 0 && found) { |
|
931 // offset = 0 => LI itself has the value attribute and we did not need to traverse back. |
|
932 // Just serialize value attribute like other tags. |
|
933 SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"), valueStr, aStr, false); |
|
934 } |
|
935 else if (offset == 1 && !found) { |
|
936 /*(offset = 1 && !found) means either LI is the first child node of OL |
|
937 and LI is not having "value" attribute. |
|
938 In that case we would not like to set "value" attribute to reduce the changes. |
|
939 */ |
|
940 //do nothing... |
|
941 } |
|
942 else if (offset > 0) { |
|
943 // Set value attribute. |
|
944 nsAutoString valueStr; |
|
945 |
|
946 //As serializer needs to use this valueAttr we are creating here, |
|
947 valueStr.AppendInt(startVal + offset); |
|
948 SerializeAttr(EmptyString(), NS_LITERAL_STRING("value"), valueStr, aStr, false); |
|
949 } |
|
950 } |
|
951 |
|
952 bool |
|
953 nsXHTMLContentSerializer::IsFirstChildOfOL(nsIContent* aElement) |
|
954 { |
|
955 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement); |
|
956 nsAutoString parentName; |
|
957 |
|
958 nsCOMPtr<nsIDOMNode> parentNode; |
|
959 node->GetParentNode(getter_AddRefs(parentNode)); |
|
960 if (parentNode) |
|
961 parentNode->GetNodeName(parentName); |
|
962 else |
|
963 return false; |
|
964 |
|
965 if (parentName.LowerCaseEqualsLiteral("ol")) { |
|
966 |
|
967 if (!mOLStateStack.IsEmpty()) { |
|
968 olState state = mOLStateStack[mOLStateStack.Length()-1]; |
|
969 if (state.isFirstListItem) |
|
970 return true; |
|
971 } |
|
972 |
|
973 return false; |
|
974 } |
|
975 else |
|
976 return false; |
|
977 } |
|
978 |
|
979 bool |
|
980 nsXHTMLContentSerializer::HasNoChildren(nsIContent * aContent) { |
|
981 |
|
982 for (nsIContent* child = aContent->GetFirstChild(); |
|
983 child; |
|
984 child = child->GetNextSibling()) { |
|
985 |
|
986 if (!child->IsNodeOfType(nsINode::eTEXT)) |
|
987 return false; |
|
988 |
|
989 if (child->TextLength()) |
|
990 return false; |
|
991 } |
|
992 |
|
993 return true; |
|
994 } |