//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package flashx.textLayout.conversion { import flash.utils.getQualifiedClassName; import flashx.textLayout.debug.assert; import flashx.textLayout.elements.ContainerFormattedElement; import flashx.textLayout.elements.FlowElement; import flashx.textLayout.elements.FlowGroupElement; import flashx.textLayout.elements.LinkElement; import flashx.textLayout.elements.ParagraphFormattedElement; import flashx.textLayout.elements.SpanElement; import flashx.textLayout.elements.TextFlow; import flashx.textLayout.formats.ITextLayoutFormat; import flashx.textLayout.formats.TextLayoutFormat; import flashx.textLayout.formats.WhiteSpaceCollapse; import flashx.textLayout.property.Property; import flashx.textLayout.tlf_internal; use namespace tlf_internal; [ExcludeClass] /** * @private * Export filter for TextLayout format. */ internal class BaseTextLayoutExporter implements ITextExporter { private var _config:ImportExportConfiguration; private var _rootTag:XML; private var _ns:Namespace; public function BaseTextLayoutExporter(ns:Namespace, rootTag:XML, config:ImportExportConfiguration) { _config = config; _ns = ns; _rootTag = rootTag; } /** Clear results from last export. */ protected function clear():void { // does nothing } /** Export text content * @param source the text to export * @param conversionType what type to return * @return Object the exported content */ public function export(source:TextFlow, conversionType:String):Object { clear(); if (conversionType == ConversionType.STRING_TYPE) return exportToString(source); else if (conversionType == ConversionType.XML_TYPE) return exportToXML(source); return null; } /** Export text content of a TextFlow into XFL format. * @param source the text to export * @return XML the exported content */ protected function exportToXML(textFlow:TextFlow) : XML { var result:XML; if (_rootTag) { result = new XML(_rootTag); result.addNamespace(_ns); result.appendChild(exportChild(textFlow)); } else { result = XML(exportTextFlow(this, textFlow)); result.addNamespace(_ns); } return result; } /** Export text content as a string * @param source the text to export * @return String the exported content */ protected function exportToString(source:TextFlow):String { var result:String; // We do some careful type casting here so that leading and trailing spaces in the XML don't // get dropped when it is converted to a string var originalSettings:Object = XML.settings(); try { XML.ignoreProcessingInstructions = false; XML.ignoreWhitespace = false; XML.prettyPrinting = false; result = exportToXML(source).toXMLString(); XML.setSettings(originalSettings); } catch(e:Error) { XML.setSettings(originalSettings); throw(e); } return result; } /** Base functionality for exporting a FlowElement. * @param exporter Root object for the export * @param flowElement Element to export * @return XMLList XML for the element */ static public function exportFlowElement(exporter:BaseTextLayoutExporter, flowElement:FlowElement):XMLList { return exporter.exportFlowElement(flowElement); } /** Overridable worker method for exporting a FlowElement. Creates the XMLList. * @param flowElement Element to export * @return XMLList XML for the element */ protected function exportFlowElement (flowElement:FlowElement):XMLList { var className:String = flash.utils.getQualifiedClassName(flowElement); var elementName:String = _config.lookupName(className); var output:XML = <{elementName}/>; output.setNamespace(_ns); return XMLList(output); } static public function exportSpanText(destination:XML, span:SpanElement, replacementRegex:RegExp, replacementXMLCallback:Function):void { //get the text for this span var spanText:String = span.text; // Check to see if it has text that needs to be converted var matchLocation:Array = spanText.match(replacementRegex); if(matchLocation) { var dummy:XML; // We have text that has characters to be converted. Break it up into runs of text interspersed with elements corresponding to match these characters while(matchLocation != null) { var ix:int = matchLocation.index; var tempStr:String = spanText.substr(0, ix); //if we have some text which does not need to be replaced, then write it now if(tempStr.length > 0) { // output[0].appendChild(tempStr); // extraneous tags can appear around a string child added after an XML element: see bug 1852072 // workaround for above-mentioned bug dummy = ; dummy.appendChild(tempStr); // no extraneous tags here since there is no preceding XML element sibling destination.appendChild(dummy.text()[0]); } var replacementXML:XML = replacementXMLCallback(spanText.charAt(ix)); CONFIG::debug{ assert(replacementXML != null, "Specified match regex, but provided null replacement XML"); } destination.appendChild(replacementXML); //remove the text up to this point spanText = spanText.slice(ix + 1, spanText.length); //look for another character to be replaced matchLocation = spanText.match(replacementRegex); //if we don't have any more matches, but there is still text, write that out as the last span if(!matchLocation && spanText.length > 0) { // output[0].appendChild(spanText); // extraneous tags can appear around a string child added after an XML element: see bug 1852072 // workaround for above-mentioned bug dummy = ; dummy.appendChild(spanText); // no extraneous tags here since there is no preceding XML element sibling destination.appendChild(dummy.text()[0]); } } } else { //this is the simple case where we don't have a character to replace destination.appendChild(span.text); } } /** Base functionality for exporting a Span. Exports as a FlowElement, * and exports the text of the span. * @param exporter Root object for the export * @param span Element to export * @return XMLList XML for the element */ static public function exportSpan(exporter:BaseTextLayoutExporter, span:SpanElement):XMLList { var output:XMLList = exportFlowElement(exporter, span); exportSpanText(output[0], span, exporter.spanTextReplacementRegex, exporter.getSpanTextReplacementXML); return output; } static private const brRegEx:RegExp = /\u2028/; /** Gets the regex that specifies characters in span text to be replaced with XML elements * Note: Each match is a single character */ protected function get spanTextReplacementRegex():RegExp { return brRegEx; } /** Gets the xml element used to represent a character in the export format */ protected function getSpanTextReplacementXML(ch:String):XML { CONFIG::debug {assert(ch == '\u2028', "Did not recognize character to be replaced with XML"); } var breakXML:XML =
; breakXML.setNamespace(flowNS); return breakXML; } /** Base functionality for exporting a FlowGroupElement. Exports as a FlowElement, * and exports the children of a element. * @param exporter Root object for the export * @param flowBlockElement Element to export * @return XMLList XML for the element */ static public function exportFlowGroupElement(exporter:BaseTextLayoutExporter, flowBlockElement:FlowGroupElement):XMLList { var output:XMLList = exportFlowElement(exporter, flowBlockElement); // output each child for(var childIter:int = 0; childIter < flowBlockElement.numChildren; ++childIter) { var flowChild:FlowElement = flowBlockElement.getChildAt(childIter); var childXML:XMLList = exporter.exportChild(flowChild); if (childXML) output.appendChild(childXML); } return output; } /** Base functionality for exporting a ParagraphFormattedElement. Exports as a FlowGroupElement, * and exports paragraph attributes. * @param exporter Root object for the export * @param flowParagraph Element to export * @return XMLList XML for the element */ static public function exportParagraphFormattedElement(exporter:BaseTextLayoutExporter, flowParagraph:ParagraphFormattedElement):XMLList { return exporter.exportParagraphFormattedElement(flowParagraph); } /** Overridable worker method for exporting a ParagraphFormattedElement. Creates the XMLList. * @param flowElement Element to export * @return XMLList XML for the element */ protected function exportParagraphFormattedElement(flowElement:FlowElement):XMLList { var rslt:XMLList = exportFlowElement(flowElement); // output each child for(var childIter:int = 0; childIter < ParagraphFormattedElement(flowElement).numChildren; ++childIter) { var flowChild:FlowElement = ParagraphFormattedElement(flowElement).getChildAt(childIter); rslt.appendChild(exportChild(flowChild)); } return rslt; } /** Base functionality for exporting a ContainerFormattedElement. Exports as a ParagraphFormattedElement, * and exports container attributes. * @param exporter Root object for the export * @param container Element to export * @return XMLList XML for the element */ static public function exportContainerFormattedElement(exporter:BaseTextLayoutExporter, container:ContainerFormattedElement):XMLList { return exporter.exportContainerFormattedElement(container); } /** Overridable worker method for exporting a ParagraphFormattedElement. Creates the XMLList. * @param flowElement Element to export * @return XMLList XML for the element */ protected function exportContainerFormattedElement(flowElement:FlowElement):XMLList { return exportParagraphFormattedElement(flowElement); } /** Base functionality for exporting a TextFlow. Exports as a ContainerElement, * and exports container attributes. * @param exporter Root object for the export * @param textFlow Element to export * @return XMLList XML for the element */ static public function exportTextFlow(exporter:BaseTextLayoutExporter, textFlow:TextFlow):XMLList { var output:XMLList = exportContainerFormattedElement(exporter, textFlow); // TextLayout will use PRESERVE on output output.@[TextLayoutFormat.whiteSpaceCollapseProperty.name] = WhiteSpaceCollapse.PRESERVE; return output; } /** Exports the object. It will find the appropriate exporter and use it to * export the object. * @param exporter Root object for the export * @param flowElement Element to export * @return XMLList XML for the flowElement */ public function exportChild(flowElement:FlowElement):XMLList { var className:String = flash.utils.getQualifiedClassName(flowElement); var info:FlowElementInfo = _config.lookupByClass(className); if (info != null) return info.exporter(this, flowElement); return null; } private function exportObjectAsDictionary(key:String,styleDict:Object):XMLList { // link attributes only right now if (key != LinkElement.LINK_NORMAL_FORMAT_NAME && key != LinkElement.LINK_ACTIVE_FORMAT_NAME && key != LinkElement.LINK_HOVER_FORMAT_NAME) return null; // create the TextLayoutFormat element var elementName:String = "TextLayoutFormat"; var formatXML:XML = <{elementName}/>; formatXML.setNamespace(flowNS); exportStyles(XMLList(formatXML), styleDict, formatDescription); // create the link format element var linkFormatXML:XMLList = XMLList(<{key}/>); linkFormatXML.appendChild(formatXML); return linkFormatXML; } /** Helper function to export styles (core or user) in the form of xml attributes or xml children * * @param xml object to which attributes/children are added * @styles the styles object: core styles, user styles, or a style dictionary * @param description attribute class metadata object; must be specified for core styles, not otherwise * @param exclusions values to be excluded from being exported */ protected function exportStyles(xml:XMLList, styles:Object, description:Object=null, exclusions:Array=null):void { var sortableStyles:Array = []; for (var key:Object in styles) { var val:Object = styles[key]; if (!exclusions || exclusions.indexOf(val) == -1) { if (description) { // Core style // Use the description object to filter out styles that should not be exported // and to obtain the corresponding String to be used as an XML attribute value var prop:Property = description[key]; if (prop) sortableStyles.push({xmlName:key, xmlVal:prop.toXMLString(val)}); } else { // User style if ((val is String) || val.hasOwnProperty("toString")) { // Is or can be converted to a String which will be used as an XML attribute value sortableStyles.push({xmlName:key, xmlVal:val}); } else { // A style dictionary; Will be converted to an XMLList containing elements to be added as children var customDictProp:XMLList = exportObjectAsDictionary(key as String,val); if (customDictProp) sortableStyles.push({xmlName:key, xmlVal:customDictProp}); } } } } // Sort the styles based on name for predictable export order sortableStyles.sortOn("xmlName"); for each(var exportInfo:Object in sortableStyles) { var xmlVal:Object = exportInfo.xmlVal; if (xmlVal is String) xml.@[exportInfo.xmlName] = xmlVal; // as an attribute else if (xmlVal is XMLList) xml.appendChild(xmlVal); // as a child } } internal function get flowNS():Namespace { return _ns; } protected function get formatDescription():Object { return null; } } }