| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // 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 __AS3__.vec.Vector; |
| |
| import flash.display.Shape; |
| import flash.text.engine.TextRotation; |
| import flash.utils.Dictionary; |
| |
| import flashx.textLayout.container.ContainerController; |
| import flashx.textLayout.debug.assert; |
| import flashx.textLayout.elements.BreakElement; |
| import flashx.textLayout.elements.DivElement; |
| import flashx.textLayout.elements.FlowElement; |
| import flashx.textLayout.elements.FlowGroupElement; |
| import flashx.textLayout.elements.GlobalSettings; |
| import flashx.textLayout.elements.IConfiguration; |
| import flashx.textLayout.elements.InlineGraphicElement; |
| import flashx.textLayout.elements.LinkElement; |
| import flashx.textLayout.elements.ParagraphElement; |
| import flashx.textLayout.elements.SpanElement; |
| import flashx.textLayout.elements.SubParagraphGroupElement; |
| import flashx.textLayout.elements.TCYElement; |
| import flashx.textLayout.elements.TabElement; |
| import flashx.textLayout.elements.TextFlow; |
| import flashx.textLayout.formats.ITextLayoutFormat; |
| import flashx.textLayout.formats.TextLayoutFormat; |
| import flashx.textLayout.formats.TextLayoutFormatValueHolder; |
| import flashx.textLayout.property.EnumStringProperty; |
| import flashx.textLayout.property.NumberProperty; |
| import flashx.textLayout.property.StringProperty; |
| import flashx.textLayout.tlf_internal; |
| |
| use namespace tlf_internal; |
| |
| [ExcludeClass] |
| /** |
| * @private |
| * TextLayoutImporter converts from XML to TextLayout data structures and back. |
| */ |
| public class TextLayoutImporter extends BaseTextLayoutImporter |
| { |
| private static var _defaultConfiguration:ImportExportConfiguration; |
| |
| /** Default ImportExportConfiguration to use when none specified |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public static function get defaultConfiguration():ImportExportConfiguration |
| { |
| // The first call will force the import/export to include the standard components |
| if (!_defaultConfiguration) |
| { |
| _defaultConfiguration = new ImportExportConfiguration(); |
| // shared by TextLayout and FXG markup |
| _defaultConfiguration.addIEInfo("TextFlow", TextFlow, BaseTextLayoutImporter.parseTextFlow, BaseTextLayoutExporter.exportTextFlow, true); |
| _defaultConfiguration.addIEInfo("br", BreakElement, BaseTextLayoutImporter.parseBreak, BaseTextLayoutExporter.exportFlowElement, false); |
| _defaultConfiguration.addIEInfo("p", ParagraphElement, BaseTextLayoutImporter.parsePara, BaseTextLayoutExporter.exportParagraphFormattedElement, true); |
| _defaultConfiguration.addIEInfo("span", SpanElement, BaseTextLayoutImporter.parseSpan, BaseTextLayoutExporter.exportSpan, false); |
| _defaultConfiguration.addIEInfo("tab", TabElement, BaseTextLayoutImporter.parseTab, BaseTextLayoutExporter.exportFlowElement, false); |
| // shared by TextLayoutMarkup only |
| _defaultConfiguration.addIEInfo("tcy", TCYElement, TextLayoutImporter.parseTCY, TextLayoutExporter.exportTCY, false); |
| _defaultConfiguration.addIEInfo("a", LinkElement, TextLayoutImporter.parseLink, TextLayoutExporter.exportLink, false); |
| _defaultConfiguration.addIEInfo("div", DivElement, TextLayoutImporter.parseDivElement, TextLayoutExporter.exportDiv, true); |
| _defaultConfiguration.addIEInfo("img", InlineGraphicElement, TextLayoutImporter.parseInlineGraphic, TextLayoutExporter.exportImage, false); |
| // customized link formats |
| _defaultConfiguration.addIEInfo(LinkElement.LINK_NORMAL_FORMAT_NAME,null,TextLayoutImporter.parseLinkNormalFormat,null, false); |
| _defaultConfiguration.addIEInfo(LinkElement.LINK_ACTIVE_FORMAT_NAME,null,TextLayoutImporter.parseLinkActiveFormat,null, false); |
| _defaultConfiguration.addIEInfo(LinkElement.LINK_HOVER_FORMAT_NAME, null,TextLayoutImporter.parseLinkHoverFormat, null, false); |
| } |
| |
| return _defaultConfiguration; |
| } |
| |
| /** Set the default configuration back to its original value |
| * @playerversion Flash 10 |
| * @playerversion AIR 1.5 |
| * @langversion 3.0 |
| */ |
| public static function restoreDefaults():void |
| { |
| _defaultConfiguration = null; |
| } |
| |
| |
| protected var bindingsArray:Array; |
| |
| static private const _formatImporter:TLFormatImporter = new TLFormatImporter(TextLayoutFormatValueHolder,TextLayoutFormat.description); |
| static private const _idImporter:SingletonAttributeImporter = new SingletonAttributeImporter("id"); |
| static private const _styleNameImporter:SingletonAttributeImporter = new SingletonAttributeImporter("styleName"); |
| static private const _customFormatImporter:CustomFormatImporter = new CustomFormatImporter(); |
| |
| static private const _flowElementFormatImporters:Array = [ _formatImporter,_idImporter,_styleNameImporter,_customFormatImporter ]; |
| |
| /** Constructor */ |
| public function TextLayoutImporter(textFlowConfiguration:IConfiguration) |
| { |
| super(textFlowConfiguration, flowNS, defaultConfiguration); |
| } |
| |
| override protected function clear():void |
| { |
| bindingsArray = null; |
| super.clear(); |
| } |
| |
| private static function get flowNS():Namespace |
| { |
| return new Namespace("flow", "http://ns.adobe.com/textLayout/2008"); |
| } |
| |
| /** @private */ |
| override protected function parseContent(rootStory:XML):TextFlow |
| { |
| // Capture all the top-level tags of interest that can be "bound" |
| // We have to do this because the attributes are applied at the point |
| // of calling something like: |
| // span.charAttrs = characterAttrs; |
| // At one time, we just set the variable to the parameter (in the setter), |
| // but now we're copying the data into a new object. This change does |
| // not allow for us to parse the bindings in any order. Hence, we |
| // will process the potential bindings objects first, then the |
| // TextFlow objects. |
| // |
| // Also note the use of "..*" below. We are using this to traverse the |
| // XML structure looking for particular tags and at the same time allow for |
| // any namespace. So, you might see something like <flow:TextContainer> or |
| // <TextContainer> and this code will capture both cases. |
| |
| var rootName:String = rootStory.name().localName; |
| var textFlowElement:XML = rootName == "TextFlow" ? rootStory : rootStory..*::TextFlow[0]; |
| if (!textFlowElement) |
| { |
| reportError(GlobalSettings.resourceStringFunction("missingTextFlow")); |
| return null; |
| } |
| if (!checkNamespace(textFlowElement)) |
| return null; |
| |
| return parseTextFlow(this, textFlowElement); |
| } |
| |
| private function parseStandardFlowElementAttributes(flowElem:FlowElement,xmlToParse:XML,importers:Array = null):void |
| { |
| if (importers == null) |
| importers = _flowElementFormatImporters; |
| // all the standard ones have to be in importers - some check needed |
| parseAttributes(xmlToParse,importers); |
| flowElem.format = extractTextFormatAttributesHelper(flowElem.format,_formatImporter) as ITextLayoutFormat; |
| |
| flowElem.id = _idImporter.result as String; |
| flowElem.styleName = _styleNameImporter.result as String; |
| flowElem.userStyles = _customFormatImporter.result as Dictionary; |
| } |
| |
| |
| override public function createTextFlowFromXML(xmlToParse:XML, textFlow:TextFlow = null):TextFlow |
| { |
| // allocate the TextFlow and set the TextContainer's rootElement to it. |
| var newFlow:TextFlow = null; |
| if (xmlToParse.@["id"] != undefined) |
| { |
| var flowName:String = null; |
| flowName = xmlToParse.@["id"]; |
| newFlow = getBoundObjNamed(flowName, TextFlow) as TextFlow; |
| } |
| |
| if (!checkNamespace(xmlToParse)) |
| return newFlow; |
| |
| // allocate the TextFlow and initialize the container attributes |
| if (!newFlow) |
| newFlow = new TextFlow(_textFlowConfiguration); |
| |
| parseStandardFlowElementAttributes(newFlow,xmlToParse); |
| |
| // TextFlow can have CharacterFormat, ParagraphFormat and ContainerFormat children. Filter them out here |
| parseFlowGroupElementChildren(xmlToParse, newFlow); |
| |
| CONFIG::debug { newFlow.debugCheckNormalizeAll() ; } |
| newFlow.tlf_internal::normalize(); |
| |
| newFlow.tlf_internal::applyWhiteSpaceCollapse(); |
| |
| return newFlow; |
| } |
| |
| public function createDivFromXML(xmlToParse:XML):DivElement |
| { |
| // add the div element to the parent |
| var divElem:DivElement = new DivElement(); |
| |
| parseStandardFlowElementAttributes(divElem,xmlToParse); |
| |
| return divElem; |
| } |
| |
| public override function createParagraphFromXML(xmlToParse:XML):ParagraphElement |
| { |
| var paraElem:ParagraphElement = new ParagraphElement(); |
| parseStandardFlowElementAttributes(paraElem,xmlToParse); |
| return paraElem; |
| } |
| |
| public function createTCYFromXML(xmlToParse:XML):TCYElement |
| { |
| var tcyElem:TCYElement = new TCYElement(); |
| parseStandardFlowElementAttributes(tcyElem,xmlToParse); |
| return tcyElem; |
| } |
| |
| |
| static internal const _linkDescription:Object = { |
| href : new StringProperty("href",null, false, null), |
| target : new StringProperty("target",null, false, null) |
| } |
| static private const _linkFormatImporter:TLFormatImporter = new TLFormatImporter(Dictionary,_linkDescription); |
| static private const _linkElementFormatImporters:Array = [ _linkFormatImporter, _formatImporter,_idImporter,_styleNameImporter,_customFormatImporter ]; |
| |
| /** Parse a LinkElement Block. |
| * |
| * @param - importFilter:BaseTextLayoutImporter - parser object |
| * @param - xmlToParse:XML - the xml describing the Link |
| * @param - parent:FlowBlockElement - the parent of the new Link |
| * @return LinkElement - a new LinkElement and its children |
| */ |
| public function createLinkFromXML(xmlToParse:XML):LinkElement |
| { |
| var linkElem:LinkElement = new LinkElement(); |
| parseStandardFlowElementAttributes(linkElem,xmlToParse,_linkElementFormatImporters); |
| if (_linkFormatImporter.result) |
| { |
| linkElem.href = _linkFormatImporter.result["href"] as String; |
| linkElem.target = _linkFormatImporter.result["target"] as String; |
| } |
| |
| return linkElem; |
| } |
| |
| public override function createSpanFromXML(xmlToParse:XML):SpanElement |
| { |
| var spanElem:SpanElement = new SpanElement(); |
| |
| parseStandardFlowElementAttributes(spanElem,xmlToParse); |
| |
| return spanElem; |
| } |
| |
| static private const _imageDescription:Object = { |
| height:InlineGraphicElement.heightPropertyDefinition, |
| width:InlineGraphicElement.widthPropertyDefinition, |
| source: new StringProperty("source", null, false, null), |
| float: new StringProperty("float", null, false, null), |
| rotation: InlineGraphicElement.rotationPropertyDefinition } |
| |
| static private const _ilgFormatImporter:TLFormatImporter = new TLFormatImporter(Dictionary,_imageDescription); |
| static private const _ilgElementFormatImporters:Array = [ _ilgFormatImporter, _formatImporter/*,_boundTextLayoutFormatImporter*/,_idImporter,_styleNameImporter,_customFormatImporter ]; |
| |
| public function createInlineGraphicFromXML(xmlToParse:XML):InlineGraphicElement |
| { |
| var imgElem:InlineGraphicElement = new InlineGraphicElement(); |
| |
| parseStandardFlowElementAttributes(imgElem,xmlToParse,_ilgElementFormatImporters); |
| |
| if (_ilgFormatImporter.result) |
| { |
| var source:String = _ilgFormatImporter.result["source"]; |
| imgElem.source = source; |
| |
| // if not defined then let InlineGraphic set its own default |
| imgElem.height = InlineGraphicElement.heightPropertyDefinition.setHelper(imgElem.height,_ilgFormatImporter.result["height"]); |
| imgElem.width = InlineGraphicElement.widthPropertyDefinition.setHelper(imgElem.width,_ilgFormatImporter.result["width"]); |
| /* We don't support rotation yet because of bugs in the player. */ |
| // imgElem.rotation = InlineGraphicElement.heightPropertyDefinition.setHelper(imgElem.rotation,_ilgFormatImporter.result["rotation"]); |
| imgElem.float = InlineGraphicElement.floatPropertyDefinition.setHelper(imgElem.float,_ilgFormatImporter.result["float"]); |
| } |
| |
| return imgElem; |
| } |
| |
| public function extractTextFormatAttributesHelper(curAttrs:Object, importer:TLFormatImporter):Object |
| { |
| return extractAttributesHelper(curAttrs,importer); |
| } |
| |
| protected function parseNamedFormatDefinition(xmlToParse:XML, importer:TLFormatImporter) : void |
| { |
| if (!checkNamespace(xmlToParse)) |
| return; |
| |
| var idName:String = xmlToParse.@id.toString(); |
| if (idName == null || idName.length == 0) |
| return; |
| |
| importer.reset(); |
| for each (var item:XML in xmlToParse.attributes()) |
| importer.importOneFormat(item.name().localName,item.toString()); |
| |
| if (!bindingsArray) |
| bindingsArray = new Array(); |
| bindingsArray[idName] = importer.result ? importer.result : new importer.classType(); |
| } |
| |
| // Find string in array |
| static private function arrayHasString(arr:Array, str:String):Boolean |
| { |
| for each (var item:String in arr) |
| if (str == item) |
| return true; |
| return false; |
| } |
| |
| // Return a "bindings" object. This method allows us to encounter a object via a binding |
| // (it appears within "{}" or we can encounter the actual tag (e.g, <CharacterFormat ... >). Either way |
| // this method will allocate an object with the given name, and assign the attributes as they become |
| // available in the BaseTextLayoutImportFilter. |
| internal function getBoundObjNamed(name:String, typeClass:Class):Object |
| { |
| CONFIG::debug {assert(name != null && name.length > 0, "null string for a bound object")} |
| if (!bindingsArray) |
| bindingsArray = new Array(); |
| if (bindingsArray[name] == null) |
| { |
| if (typeClass == TextFlow) |
| bindingsArray[name] = new typeClass(this._textFlowConfiguration); |
| else |
| bindingsArray[name] = new typeClass(); |
| } |
| |
| return bindingsArray[name]; |
| } |
| |
| /** Parse a TCY Block. |
| * |
| * @param - importFilter:BaseTextLayoutImporter - parser object |
| * @param - xmlToParse:XML - the xml describing the TCY Block |
| * @param - parent:FlowBlockElement - the parent of the new TCY Block |
| * @return TCYBlockElement - a new TCYBlockElement and its children |
| */ |
| static public function parseTCY(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { |
| var tcyElem:TCYElement = TextLayoutImporter(importFilter).createTCYFromXML(xmlToParse); |
| if (importFilter.addChild(parent, tcyElem)) |
| { |
| importFilter.parseFlowGroupElementChildren(xmlToParse, tcyElem); |
| //if parsing an empty tcy, create a Span for it. |
| if (tcyElem.numChildren == 0) |
| tcyElem.addChild(new SpanElement()); |
| } |
| } |
| |
| |
| /** Parse a LinkElement Block. |
| * |
| * @param - importFilter:BaseTextLayoutImporter - parser object |
| * @param - xmlToParse:XML - the xml describing the Link |
| * @param - parent:FlowBlockElement - the parent of the new Link |
| * @return LinkElement - a new LinkElement and its children |
| */ |
| static public function parseLink(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { |
| var linkElem:LinkElement = TextLayoutImporter(importFilter).createLinkFromXML(xmlToParse); |
| if (importFilter.addChild(parent, linkElem)) |
| { |
| importFilter.parseFlowGroupElementChildren(xmlToParse, linkElem); |
| //if parsing an empty link, create a Span for it. |
| if (linkElem.numChildren == 0) |
| linkElem.addChild(new SpanElement()); |
| } |
| } |
| |
| public function createDictionaryFromXML(xmlToParse:XML):Dictionary |
| { |
| var formatImporters:Array = [ _customFormatImporter ]; |
| |
| // parse the TextLayoutFormat child object |
| var formatList:XMLList = xmlToParse..*::TextLayoutFormat; |
| if (formatList.length() != 1) |
| reportError(GlobalSettings.resourceStringFunction("expectedExactlyOneTextLayoutFormat",[ xmlToParse.name() ])); |
| |
| var parseThis:XML = formatList.length() > 0 ? formatList[0] : xmlToParse; |
| parseAttributes(parseThis,formatImporters); |
| var styleDictionary:Dictionary = _customFormatImporter.result as Dictionary; |
| |
| // Link style property values may have been brought through as literal String values. We need to convert |
| // them into typed values, so they get output as canonical translation of the value type into String. |
| // The color property is an example, where it can get input in 3 different formats, but can only be output in one. |
| var description:Object = TextLayoutFormat.description; |
| for (var prop:String in description) |
| { |
| var val:* = styleDictionary[prop]; |
| if (val !== undefined) |
| { |
| val = description[prop].setHelper(undefined,val) |
| if (val !== undefined) |
| styleDictionary[prop] = val; |
| } |
| } |
| |
| return styleDictionary; |
| } |
| |
| static public function parseLinkNormalFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { parent.linkNormalFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); } |
| static public function parseLinkActiveFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { parent.linkActiveFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); } |
| static public function parseLinkHoverFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { parent.linkHoverFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); } |
| |
| /** Parse the <div ...> tag and all its children |
| * |
| * @param - importFilter:BaseTextLayoutImportFilter - parser object |
| * @param - xmlToParse:XML - the xml describing the Div |
| * @param - parent:FlowBlockElement - the parent of the new Div |
| */ |
| static public function parseDivElement(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { |
| var divElem:DivElement = TextLayoutImporter(importFilter).createDivFromXML(xmlToParse); |
| if (importFilter.addChild(parent, divElem)) |
| { |
| importFilter.parseFlowGroupElementChildren(xmlToParse, divElem); |
| // we can't have a <div> tag w/no children... so, add an empty paragraph |
| if (divElem.numChildren == 0) |
| divElem.addChild(new ParagraphElement()); |
| } |
| } |
| |
| /** Parse a leaf element, the <img ...> tag. |
| * |
| * @param - importFilter:BaseTextLayoutImporter - parser object |
| * @param - xmlToParse:XML - the xml describing the InlineGraphic FlowElement |
| * @param - parent:FlowBlockElement - the parent of the new image FlowElement |
| */ |
| static public function parseInlineGraphic(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void |
| { |
| var ilg:InlineGraphicElement = TextLayoutImporter(importFilter).createInlineGraphicFromXML(xmlToParse); |
| importFilter.addChild(parent, ilg); |
| } |
| } |
| } |
| |