📄 markup.cpp
字号:
Element* e = static_cast<Element*>(n); NamedAttrMap* attrs = e->attributes(); unsigned length = attrs->length(); for (unsigned i = 0; i < length; i++) { Attribute* attr = attrs->attributeItem(i); if (e->isURLAttribute(attr)) changes.append(AttributeChange(e, attr->name(), KURL(parsedBaseURL, attr->value()).string())); } } } size_t numChanges = changes.size(); for (size_t i = 0; i < numChanges; ++i) changes[i].apply();}static bool needInterchangeNewlineAfter(const VisiblePosition& v){ VisiblePosition next = v.next(); Node* upstreamNode = next.deepEquivalent().upstream().node(); Node* downstreamNode = v.deepEquivalent().downstream().node(); // Add an interchange newline if a paragraph break is selected and a br won't already be added to the markup to represent it. return isEndOfParagraph(v) && isStartOfParagraph(next) && !(upstreamNode->hasTagName(brTag) && upstreamNode == downstreamNode);}static PassRefPtr<CSSMutableStyleDeclaration> styleFromMatchedRulesAndInlineDecl(const Node* node){ if (!node->isHTMLElement()) return 0; // FIXME: Having to const_cast here is ugly, but it is quite a bit of work to untangle // the non-const-ness of styleFromMatchedRulesForElement. HTMLElement* element = const_cast<HTMLElement*>(static_cast<const HTMLElement*>(node)); RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesForElement(element); RefPtr<CSSMutableStyleDeclaration> inlineStyleDecl = element->getInlineStyleDecl(); style->merge(inlineStyleDecl.get()); return style.release();}static bool propertyMissingOrEqualToNone(CSSMutableStyleDeclaration* style, int propertyID){ if (!style) return false; RefPtr<CSSValue> value = style->getPropertyCSSValue(propertyID); if (!value) return true; if (!value->isPrimitiveValue()) return false; return static_cast<CSSPrimitiveValue*>(value.get())->getIdent() == CSSValueNone;}static bool elementHasTextDecorationProperty(const Node* node){ RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(node); if (!style) return false; return !propertyMissingOrEqualToNone(style.get(), CSSPropertyTextDecoration);}static String joinMarkups(const Vector<String>& preMarkups, const Vector<String>& postMarkups){ size_t length = 0; size_t preCount = preMarkups.size(); for (size_t i = 0; i < preCount; ++i) length += preMarkups[i].length(); size_t postCount = postMarkups.size(); for (size_t i = 0; i < postCount; ++i) length += postMarkups[i].length(); Vector<UChar> result; result.reserveInitialCapacity(length); for (size_t i = preCount; i > 0; --i) append(result, preMarkups[i - 1]); for (size_t i = 0; i < postCount; ++i) append(result, postMarkups[i]); return String::adopt(result);}static bool isSpecialAncestorBlock(Node* node){ if (!node || !isBlock(node)) return false; return node->hasTagName(listingTag) || node->hasTagName(olTag) || node->hasTagName(preTag) || node->hasTagName(tableTag) || node->hasTagName(ulTag) || node->hasTagName(xmpTag) || node->hasTagName(h1Tag) || node->hasTagName(h2Tag) || node->hasTagName(h3Tag) || node->hasTagName(h4Tag) || node->hasTagName(h5Tag);}// FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString()String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines){ DEFINE_STATIC_LOCAL(const String, interchangeNewlineString, ("<br class=\"" AppleInterchangeNewline "\">")); if (!range) return ""; Document* document = range->ownerDocument(); if (!document) return ""; bool documentIsHTML = document->isHTMLDocument(); // Disable the delete button so it's elements are not serialized into the markup, // but make sure neither endpoint is inside the delete user interface. Frame* frame = document->frame(); DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0; RefPtr<Range> updatedRange = avoidIntersectionWithNode(range, deleteButton ? deleteButton->containerElement() : 0); if (!updatedRange) return ""; if (deleteButton) deleteButton->disable(); ExceptionCode ec = 0; bool collapsed = updatedRange->collapsed(ec); ASSERT(ec == 0); if (collapsed) return ""; Node* commonAncestor = updatedRange->commonAncestorContainer(ec); ASSERT(ec == 0); if (!commonAncestor) return ""; document->updateLayoutIgnorePendingStylesheets(); Vector<String> markups; Vector<String> preMarkups; Node* pastEnd = updatedRange->pastLastNode(); Node* lastClosed = 0; Vector<Node*> ancestorsToClose; Node* startNode = updatedRange->firstNode(); VisiblePosition visibleStart(updatedRange->startPosition(), VP_DEFAULT_AFFINITY); VisiblePosition visibleEnd(updatedRange->endPosition(), VP_DEFAULT_AFFINITY); if (annotate && needInterchangeNewlineAfter(visibleStart)) { if (visibleStart == visibleEnd.previous()) { if (deleteButton) deleteButton->enable(); return interchangeNewlineString; } markups.append(interchangeNewlineString); startNode = visibleStart.next().deepEquivalent().node(); } Node* next; for (Node* n = startNode; n != pastEnd; n = next) { // According to <rdar://problem/5730668>, it is possible for n to blow past pastEnd and become null here. This // shouldn't be possible. This null check will prevent crashes (but create too much markup) and the ASSERT will // hopefully lead us to understanding the problem. ASSERT(n); if (!n) break; next = n->traverseNextNode(); bool skipDescendants = false; bool addMarkupForNode = true; if (!n->renderer() && !enclosingNodeWithTag(Position(n, 0), selectTag)) { skipDescendants = true; addMarkupForNode = false; next = n->traverseNextSibling(); // Don't skip over pastEnd. if (pastEnd && pastEnd->isDescendantOf(n)) next = pastEnd; } if (isBlock(n) && canHaveChildrenForEditing(n) && next == pastEnd) // Don't write out empty block containers that aren't fully selected. continue; // Add the node to the markup. if (addMarkupForNode) { markups.append(getStartMarkup(n, updatedRange.get(), annotate)); if (nodes) nodes->append(n); } if (n->firstChild() == 0 || skipDescendants) { // Node has no children, or we are skipping it's descendants, add its close tag now. if (addMarkupForNode) { markups.append(getEndMarkup(n)); lastClosed = n; } // Check if the node is the last leaf of a tree. if (!n->nextSibling() || next == pastEnd) { if (!ancestorsToClose.isEmpty()) { // Close up the ancestors. do { Node *ancestor = ancestorsToClose.last(); if (next != pastEnd && next->isDescendantOf(ancestor)) break; // Not at the end of the range, close ancestors up to sibling of next node. markups.append(getEndMarkup(ancestor)); lastClosed = ancestor; ancestorsToClose.removeLast(); } while (!ancestorsToClose.isEmpty()); } // Surround the currently accumulated markup with markup for ancestors we never opened as we leave the subtree(s) rooted at those ancestors. Node* nextParent = next ? next->parentNode() : 0; if (next != pastEnd && n != nextParent) { Node* lastAncestorClosedOrSelf = n->isDescendantOf(lastClosed) ? lastClosed : n; for (Node *parent = lastAncestorClosedOrSelf->parent(); parent != 0 && parent != nextParent; parent = parent->parentNode()) { // All ancestors that aren't in the ancestorsToClose list should either be a) unrendered: if (!parent->renderer()) continue; // or b) ancestors that we never encountered during a pre-order traversal starting at startNode: ASSERT(startNode->isDescendantOf(parent)); preMarkups.append(getStartMarkup(parent, updatedRange.get(), annotate)); markups.append(getEndMarkup(parent)); if (nodes) nodes->append(parent); lastClosed = parent; } } } } else if (addMarkupForNode && !skipDescendants) // We added markup for this node, and we're descending into it. Set it to close eventually. ancestorsToClose.append(n); } // Include ancestors that aren't completely inside the range but are required to retain // the structure and appearance of the copied markup. Node* specialCommonAncestor = 0; Node* commonAncestorBlock = commonAncestor ? enclosingBlock(commonAncestor) : 0; if (annotate && commonAncestorBlock) { if (commonAncestorBlock->hasTagName(tbodyTag) || commonAncestorBlock->hasTagName(trTag)) { Node* table = commonAncestorBlock->parentNode(); while (table && !table->hasTagName(tableTag)) table = table->parentNode(); if (table) specialCommonAncestor = table; } else if (isSpecialAncestorBlock(commonAncestorBlock)) specialCommonAncestor = commonAncestorBlock; } // Retain the Mail quote level by including all ancestor mail block quotes. if (lastClosed && annotate) { for (Node *ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) if (isMailBlockquote(ancestor)) specialCommonAncestor = ancestor; } Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; if (checkAncestor->renderer()) { RefPtr<CSSMutableStyleDeclaration> checkAncestorStyle = computedStyle(checkAncestor)->copyInheritableProperties(); if (!propertyMissingOrEqualToNone(checkAncestorStyle.get(), CSSPropertyWebkitTextDecorationsInEffect)) specialCommonAncestor = enclosingNodeOfType(Position(checkAncestor, 0), &elementHasTextDecorationProperty); } // If a single tab is selected, commonAncestor will be a text node inside a tab span. // If two or more tabs are selected, commonAncestor will be the tab span. // In either case, if there is a specialCommonAncestor already, it will necessarily be above // any tab span that needs to be included. if (!specialCommonAncestor && isTabSpanTextNode(commonAncestor)) specialCommonAncestor = commonAncestor->parentNode(); if (!specialCommonAncestor && isTabSpanNode(commonAncestor)) specialCommonAncestor = commonAncestor; if (Node *enclosingAnchor = enclosingNodeWithTag(Position(specialCommonAncestor ? specialCommonAncestor : commonAncestor, 0), aTag)) specialCommonAncestor = enclosingAnchor; Node* body = enclosingNodeWithTag(Position(commonAncestor, 0), bodyTag); // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup. // FIXME: Do this for all fully selected blocks, not just the body. Node* fullySelectedRoot = body && *VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0; if (annotate && fullySelectedRoot) specialCommonAncestor = fullySelectedRoot; if (specialCommonAncestor && lastClosed) { // Also include all of the ancestors of lastClosed up to this special ancestor. for (Node* ancestor = lastClosed->parentNode(); ancestor; ancestor = ancestor->parentNode()) { if (ancestor == fullySelectedRoot && !convertBlocksToInlines) { RefPtr<CSSMutableStyleDeclaration> style = styleFromMatchedRulesAndInlineDecl(fullySelectedRoot); // Bring the background attribute over, but not as an attribute because a background attribute on a div // appears to have no effect. if (!style->getPropertyCSSValue(CSSPropertyBackgroundImage) && static_cast<Element*>(fullySelectedRoot)->hasAttribute(backgroundAttr)) style->setProperty(CSSPropertyBackgroundImage, "url('" + static_cast<Element*>(fullySelectedRoot)->getAttribute(backgroundAttr) + "')"); if (style->length()) { Vector<UChar> openTag; DEFINE_STATIC_LOCAL(const String, divStyle, ("<div style=\"")); append(openTag, divStyle); appendAttributeValue(openTag, style->cssText(), documentIsHTML); openTag.append('\"'); openTag.append('>'); preMarkups.append(String::adopt(openTag)); DEFINE_STATIC_LOCAL(const String, divCloseTag, ("</div>")); markups.append(divCloseTag); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -