From 3647b593ee92fa94e88393e74e077425ad6f460c Mon Sep 17 00:00:00 2001 From: brucetoo Date: Tue, 25 Dec 2018 17:34:01 +0800 Subject: [PATCH 01/13] [weex][android] fix the custom file ttf not work well, need replace uri.getPath() with uri.getEncodedSchemeSpecificPart(). check the code comments for detail. --- .../sdk/src/main/java/com/taobao/weex/utils/FontDO.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/android/sdk/src/main/java/com/taobao/weex/utils/FontDO.java b/android/sdk/src/main/java/com/taobao/weex/utils/FontDO.java index 076564e8c7..ae8fc0b1e7 100644 --- a/android/sdk/src/main/java/com/taobao/weex/utils/FontDO.java +++ b/android/sdk/src/main/java/com/taobao/weex/utils/FontDO.java @@ -97,7 +97,12 @@ private void parseSrc(String src, WXSDKInstance instance) { mType = TYPE_NETWORK; } else if (Constants.Scheme.FILE.equals(scheme)) { mType = TYPE_FILE; - mUrl = uri.getPath(); + /** + * eg: file://name/A/B.ttf + * getPath() = "A/B.ttf",but the real absolute path is "/name/A/B.ttf" + * so use getEncodedSchemeSpecificPart() to replaced = "//name/A/B.ttf" + */ + mUrl = uri.getEncodedSchemeSpecificPart(); } else if (Constants.Scheme.LOCAL.equals(scheme)){ mType = TYPE_LOCAL; } else if (Constants.Scheme.DATA.equals(scheme)) { From 329e269aedea0af6c71f2dc4f66b8528d426c222 Mon Sep 17 00:00:00 2001 From: brucetoo Date: Fri, 11 Jan 2019 13:36:45 +0800 Subject: [PATCH 02/13] [Android] Support RichText by a special way to render html string 1. support render by native webView,can custom html template 2. support tag to render by any view(default with ImageView) 3. integrate HtmlCompat which extract from support library into weex with many changes 4. support extract any tags, and render by any custom view --- .../weex/commons/adapter/ImageAdapter.java | 125 +- .../main/java/com/taobao/weex/InitConfig.java | 13 + .../java/com/taobao/weex/WXSDKEngine.java | 4 +- .../java/com/taobao/weex/WXSDKInstance.java | 5 + .../java/com/taobao/weex/WXSDKManager.java | 11 + .../weex/adapter/IWxHtmlTagAdapter.java | 43 + .../ui/component/WXBasicComponentType.java | 1 + .../weex/ui/component/html/AtMostWebView.java | 48 + .../weex/ui/component/html/HtmlComponent.java | 270 ++ .../html/JellyBeanSpanFixTextView.java | 211 ++ .../weex/ui/component/html/NumberSpan.java | 108 + .../ui/component/html/WxHtmlComponent.java | 183 ++ .../ui/component/html/WxHtmlTagHandler.java | 67 + .../html/adapter/DefaultHtmlTagAdapter.java | 232 ++ .../component/html/htmlcompat/ColorUtils.java | 88 + .../component/html/htmlcompat/HtmlCompat.java | 665 ++++ .../htmlcompat/HtmlToSpannedConverter.java | 920 ++++++ .../component/html/htmlcompat/XmlUtils.java | 833 +++++ .../ccil/cowan/tagsoup/AttributesImpl.java | 575 ++++ .../org/ccil/cowan/tagsoup/AutoDetector.java | 55 + .../java/org/ccil/cowan/tagsoup/Element.java | 180 + .../org/ccil/cowan/tagsoup/ElementType.java | 250 ++ .../org/ccil/cowan/tagsoup/HTMLModels.java | 65 + .../org/ccil/cowan/tagsoup/HTMLScanner.java | 687 ++++ .../org/ccil/cowan/tagsoup/HTMLSchema.java | 2907 +++++++++++++++++ .../org/ccil/cowan/tagsoup/PYXWriter.java | 204 ++ .../java/org/ccil/cowan/tagsoup/Parser.java | 1043 ++++++ .../org/ccil/cowan/tagsoup/ScanHandler.java | 104 + .../java/org/ccil/cowan/tagsoup/Scanner.java | 59 + .../java/org/ccil/cowan/tagsoup/Schema.java | 158 + 30 files changed, 10047 insertions(+), 67 deletions(-) create mode 100644 android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/AtMostWebView.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/JellyBeanSpanFixTextView.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/NumberSpan.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlComponent.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlTagHandler.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/adapter/DefaultHtmlTagAdapter.java create mode 100755 android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/ColorUtils.java create mode 100755 android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlCompat.java create mode 100755 android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlToSpannedConverter.java create mode 100755 android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/XmlUtils.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/AttributesImpl.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/AutoDetector.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/Element.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/ElementType.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLModels.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLScanner.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLSchema.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/PYXWriter.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/Parser.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/ScanHandler.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/Scanner.java create mode 100755 android/sdk/src/main/java/org/ccil/cowan/tagsoup/Schema.java diff --git a/android/commons/src/main/java/com/alibaba/weex/commons/adapter/ImageAdapter.java b/android/commons/src/main/java/com/alibaba/weex/commons/adapter/ImageAdapter.java index 77dc4fcbb6..75f2754ec4 100644 --- a/android/commons/src/main/java/com/alibaba/weex/commons/adapter/ImageAdapter.java +++ b/android/commons/src/main/java/com/alibaba/weex/commons/adapter/ImageAdapter.java @@ -18,20 +18,14 @@ */ package com.alibaba.weex.commons.adapter; -import android.net.Uri; -import android.os.Looper; -import android.text.TextUtils; import android.widget.ImageView; -import com.squareup.picasso.Callback; import com.squareup.picasso.Picasso; -import com.taobao.weex.WXEnvironment; import com.taobao.weex.WXSDKInstance; import com.taobao.weex.WXSDKManager; import com.taobao.weex.adapter.IWXImgLoaderAdapter; import com.taobao.weex.common.WXImageStrategy; import com.taobao.weex.dom.WXImageQuality; -import com.taobao.weex.ui.IFComponentHolder; public class ImageAdapter implements IWXImgLoaderAdapter { @@ -41,65 +35,66 @@ public ImageAdapter() { @Override public void setImage(final String url, final ImageView view, WXImageQuality quality, final WXImageStrategy strategy) { - Runnable runnable = new Runnable() { - - @Override - public void run() { - if(view==null||view.getLayoutParams()==null){ - return; - } - if (TextUtils.isEmpty(url)) { - view.setImageBitmap(null); - return; - } - if (null != strategy){ - recordImgLoadAction(strategy.instanceId); - } - - String temp = url; - if (url.startsWith("//")) { - temp = "http:" + url; - } - - if(!TextUtils.isEmpty(strategy.placeHolder)){ - Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication()); - Picasso picasso=builder.build(); - picasso.load(Uri.parse(strategy.placeHolder)).into(view); - - view.setTag(strategy.placeHolder.hashCode(),picasso); - } - - Picasso.with(WXEnvironment.getApplication()) - .load(temp) - .transform(new BlurTransformation(strategy.blurRadius)) - .into(view, new Callback() { - @Override - public void onSuccess() { - if(strategy.getImageListener()!=null){ - strategy.getImageListener().onImageFinish(url,view,true,null); - } - recordImgLoadResult(strategy.instanceId,true,null); - - if(!TextUtils.isEmpty(strategy.placeHolder)){ - ((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view); - } - } - - @Override - public void onError() { - if(strategy.getImageListener()!=null){ - strategy.getImageListener().onImageFinish(url,view,false,null); - } - recordImgLoadResult(strategy.instanceId,false,null); - } - }); - } - }; - if(Thread.currentThread() == Looper.getMainLooper().getThread()){ - runnable.run(); - }else { - WXSDKManager.getInstance().postOnUiThread(runnable, 0); - } + Picasso.with(view.getContext()).load(url).into(view); +// Runnable runnable = new Runnable() { +// +// @Override +// public void run() { +// if(view==null||view.getLayoutParams()==null){ +// return; +// } +// if (TextUtils.isEmpty(url)) { +// view.setImageBitmap(null); +// return; +// } +// if (null != strategy){ +// recordImgLoadAction(strategy.instanceId); +// } +// +// String temp = url; +// if (url.startsWith("//")) { +// temp = "http:" + url; +// } +// +// if(!TextUtils.isEmpty(strategy.placeHolder)){ +// Picasso.Builder builder=new Picasso.Builder(WXEnvironment.getApplication()); +// Picasso picasso=builder.build(); +// picasso.load(Uri.parse(strategy.placeHolder)).into(view); +// +// view.setTag(strategy.placeHolder.hashCode(),picasso); +// } +// +// Picasso.with(WXEnvironment.getApplication()) +// .load(temp) +// .transform(new BlurTransformation(strategy.blurRadius)) +// .into(view, new Callback() { +// @Override +// public void onSuccess() { +// if(strategy.getImageListener()!=null){ +// strategy.getImageListener().onImageFinish(url,view,true,null); +// } +// recordImgLoadResult(strategy.instanceId,true,null); +// +// if(!TextUtils.isEmpty(strategy.placeHolder)){ +// ((Picasso) view.getTag(strategy.placeHolder.hashCode())).cancelRequest(view); +// } +// } +// +// @Override +// public void onError() { +// if(strategy.getImageListener()!=null){ +// strategy.getImageListener().onImageFinish(url,view,false,null); +// } +// recordImgLoadResult(strategy.instanceId,false,null); +// } +// }); +// } +// }; +// if(Thread.currentThread() == Looper.getMainLooper().getThread()){ +// runnable.run(); +// }else { +// WXSDKManager.getInstance().postOnUiThread(runnable, 0); +// } } private void recordImgLoadAction(String instanceId){ WXSDKInstance instance = WXSDKManager.getInstance().getAllInstanceMap().get(instanceId); diff --git a/android/sdk/src/main/java/com/taobao/weex/InitConfig.java b/android/sdk/src/main/java/com/taobao/weex/InitConfig.java index d87c28f4c2..835de4168a 100644 --- a/android/sdk/src/main/java/com/taobao/weex/InitConfig.java +++ b/android/sdk/src/main/java/com/taobao/weex/InitConfig.java @@ -27,6 +27,7 @@ import com.taobao.weex.adapter.IWXJsFileLoaderAdapter; import com.taobao.weex.adapter.IWXSoLoaderAdapter; import com.taobao.weex.adapter.IWXUserTrackAdapter; +import com.taobao.weex.adapter.IWxHtmlTagAdapter; import com.taobao.weex.adapter.URIAdapter; import com.taobao.weex.appfram.storage.IWXStorageAdapter; import com.taobao.weex.appfram.websocket.IWebSocketAdapterFactory; @@ -50,6 +51,7 @@ public class InitConfig { private IApmGenerator apmGenerater; private IWXJsFileLoaderAdapter jsFileLoaderAdapter; private IWXJscProcessManager jscProcessManager; + private IWxHtmlTagAdapter htmlTagAdapter; public IWXHttpAdapter getHttpAdapter() { return httpAdapter; @@ -111,6 +113,10 @@ public IWXJscProcessManager getJscProcessManager() { return jscProcessManager; } + public IWxHtmlTagAdapter getHtmlTagAdapter() { + return htmlTagAdapter; + } + private InitConfig() { } @@ -128,6 +134,7 @@ public static class Builder{ ClassLoaderAdapter classLoaderAdapter; IApmGenerator apmGenerater; private IWXJsFileLoaderAdapter jsFileLoaderAdapter; + private IWxHtmlTagAdapter htmlTagAdapter; public IWXJscProcessManager getJscProcessManager() { return jscProcessManager; @@ -184,6 +191,11 @@ public Builder setSoLoader(IWXSoLoaderAdapter loader) { return this; } + public Builder setHtmlTagAdapter(IWxHtmlTagAdapter htmlTagAdapter){ + this.htmlTagAdapter = htmlTagAdapter; + return this; + } + public Builder setFramework(String framework){ this.framework=framework; return this; @@ -225,6 +237,7 @@ public InitConfig build(){ config.apmGenerater = this.apmGenerater; config.jsFileLoaderAdapter = this.jsFileLoaderAdapter; config.jscProcessManager = this.jscProcessManager; + config.htmlTagAdapter = this.htmlTagAdapter; return config; } } diff --git a/android/sdk/src/main/java/com/taobao/weex/WXSDKEngine.java b/android/sdk/src/main/java/com/taobao/weex/WXSDKEngine.java index 10bda0b141..f170d8c759 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXSDKEngine.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXSDKEngine.java @@ -54,7 +54,6 @@ import com.taobao.weex.common.WXException; import com.taobao.weex.common.WXInstanceWrap; import com.taobao.weex.common.WXModule; -import com.taobao.weex.common.WXPerformance; import com.taobao.weex.http.WXStreamModule; import com.taobao.weex.ui.ExternalLoaderComponentHolder; import com.taobao.weex.ui.IExternalComponentGetter; @@ -83,7 +82,7 @@ import com.taobao.weex.ui.component.WXText; import com.taobao.weex.ui.component.WXVideo; import com.taobao.weex.ui.component.WXWeb; -import com.taobao.weex.ui.component.basic.WXBasicComponent; +import com.taobao.weex.ui.component.html.WxHtmlComponent; import com.taobao.weex.ui.component.list.HorizontalListComponent; import com.taobao.weex.ui.component.list.SimpleListComponent; import com.taobao.weex.ui.component.list.WXCell; @@ -369,6 +368,7 @@ private static void register() { registerComponent(WXBasicComponentType.LOADING, WXLoading.class); registerComponent(WXBasicComponentType.LOADING_INDICATOR, WXLoadingIndicator.class); registerComponent(WXBasicComponentType.HEADER, WXHeader.class); + registerComponent(WXBasicComponentType.HTML_TEXT, WxHtmlComponent.class); registerModule("modal", WXModalUIModule.class); registerModule("instanceWrap", WXInstanceWrap.class); diff --git a/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java b/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java index 083dc90d55..b456e16e02 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXSDKInstance.java @@ -46,6 +46,7 @@ import com.taobao.weex.adapter.IWXImgLoaderAdapter; import com.taobao.weex.adapter.IWXJscProcessManager; import com.taobao.weex.adapter.IWXUserTrackAdapter; +import com.taobao.weex.adapter.IWxHtmlTagAdapter; import com.taobao.weex.adapter.URIAdapter; import com.taobao.weex.appfram.websocket.IWebSocketAdapter; import com.taobao.weex.bridge.EventResult; @@ -957,6 +958,10 @@ public IWXImgLoaderAdapter getImgLoaderAdapter() { return WXSDKManager.getInstance().getIWXImgLoaderAdapter(); } + public IWxHtmlTagAdapter getHtmlTextAdapter() { + return WXSDKManager.getInstance().getWxHtmlTextViewAdapter(); + } + public IDrawableLoader getDrawableLoader() { return WXSDKManager.getInstance().getDrawableLoader(); } diff --git a/android/sdk/src/main/java/com/taobao/weex/WXSDKManager.java b/android/sdk/src/main/java/com/taobao/weex/WXSDKManager.java index afd2a4cb99..ffc30e9982 100644 --- a/android/sdk/src/main/java/com/taobao/weex/WXSDKManager.java +++ b/android/sdk/src/main/java/com/taobao/weex/WXSDKManager.java @@ -37,6 +37,7 @@ import com.taobao.weex.adapter.IWXJsFileLoaderAdapter; import com.taobao.weex.adapter.IWXSoLoaderAdapter; import com.taobao.weex.adapter.IWXUserTrackAdapter; +import com.taobao.weex.adapter.IWxHtmlTagAdapter; import com.taobao.weex.adapter.URIAdapter; import com.taobao.weex.appfram.navigator.IActivityNavBarSetter; import com.taobao.weex.appfram.navigator.INavigator; @@ -54,6 +55,7 @@ import com.taobao.weex.performance.IApmGenerator; import com.taobao.weex.performance.IWXAnalyzer; import com.taobao.weex.ui.WXRenderManager; +import com.taobao.weex.ui.component.html.adapter.DefaultHtmlTagAdapter; import com.taobao.weex.utils.WXLogUtils; import com.taobao.weex.utils.WXUtils; @@ -85,6 +87,7 @@ public class WXSDKManager { private List mWXAnalyzerList; private IApmGenerator mApmGenerater; private IWXJsFileLoaderAdapter mWXJsFileLoaderAdapter; + private IWxHtmlTagAdapter mWxHtmlTextViewAdapter; private ICrashInfoReporter mCrashInfo; @@ -404,6 +407,14 @@ void setInitConfig(InitConfig config){ this.mApmGenerater = config.getApmGenerater(); this.mWXJsFileLoaderAdapter = config.getJsFileLoaderAdapter(); this.mWXJscProcessManager = config.getJscProcessManager(); + this.mWxHtmlTextViewAdapter = config.getHtmlTagAdapter(); + } + + public IWxHtmlTagAdapter getWxHtmlTextViewAdapter() { + if (mWxHtmlTextViewAdapter == null) { + mWxHtmlTextViewAdapter = new DefaultHtmlTagAdapter(); + } + return mWxHtmlTextViewAdapter; } public IWXStorageAdapter getIWXStorageAdapter(){ diff --git a/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java b/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java new file mode 100644 index 0000000000..8e9e12a740 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java @@ -0,0 +1,43 @@ +/** + * 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 com.taobao.weex.adapter; + +import android.content.Context; +import android.view.View; + +import com.taobao.weex.ui.component.html.HtmlComponent; +import com.taobao.weex.ui.component.html.WxHtmlComponent; + +/** Created by Bruce Too On 2019/1/10. At 09:39 */ +public interface IWxHtmlTagAdapter { + /** + * Custom html tags mapped with native view,see the default implement at {@link + * com.taobao.weex.ui.component.html.adapter.DefaultHtmlTagAdapter} + * + * @param context current activity context + * @param tagType tag's type define in {@link HtmlComponent.ViewType} + * @param html html string + * @return the custom native view self + */ + public T getHtmlTagView( + Context context, WxHtmlComponent component, @HtmlComponent.ViewType int tagType, String html); + + public View.OnClickListener getTagViewClickListener( + @HtmlComponent.ViewType int tagType, String html); +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXBasicComponentType.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXBasicComponentType.java index 936206f812..8c4e62771f 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/WXBasicComponentType.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/WXBasicComponentType.java @@ -38,6 +38,7 @@ public class WXBasicComponentType { public static final String HLIST = "hlist"; public static final String CELL = "cell"; public static final String HEADER = "header"; + public static final String HTML_TEXT = "html-text"; public static final String FOOTER = "footer"; public static final String INDICATOR = "indicator"; public static final String VIDEO = "video"; diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/AtMostWebView.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/AtMostWebView.java new file mode 100644 index 0000000000..adb972cef7 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/AtMostWebView.java @@ -0,0 +1,48 @@ +/** + * 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 com.taobao.weex.ui.component.html; + +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebView; + +/** + * Created by Bruce Too + * On 2019/1/9. + * At 13:51 + */ +public class AtMostWebView extends WebView { + public AtMostWebView(Context context) { + super(context); + } + + public AtMostWebView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public AtMostWebView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int heightSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, heightSpec); + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java new file mode 100644 index 0000000000..af279e0667 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java @@ -0,0 +1,270 @@ +/** + * 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 com.taobao.weex.ui.component.html; + +import android.support.annotation.IntDef; +import android.support.annotation.StringDef; +import android.text.TextUtils; + +import com.taobao.weex.utils.WXLogUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** Created by Bruce Too On 2019/1/7. At 17:20 */ +public class HtmlComponent { + /** + * Use default css style to render table or other tags,make it more real one Need replace + * "placeholder" with your real html tag info. + * + * @see {{@link #getHtmlTemplateForTable(String,String)}} + */ + public static final String HTML_TEMPLATE = + "\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + "
\n" + + " placeholder \n" + + "
\n" + + " \n" + + ""; + + /** + * The regex of matching + * + *
**
+ * + * **
+ */ + private static final String TABLE_REGEX = "<(tag|TAG)\\s{0,1}.+?"; + /** + * The regex of matching like + */ + private static final String SINGLE_TAG_REGEX = "<(tag|TAG)(.*?)(/>|>|>)"; + /** + * The regex of matching like src="1.jpg" if match successfully,the final value will in group(3) + */ + private static final String ATTRIBUTE_REGEX = "(key|KEY)=(\\\"|\\')(.*?)(\\\"|\\')"; + + public static final String TABLE = "table"; + public static final String VIDEO = "video"; + public static final String IMAGE = "img"; + + @StringDef({TABLE, VIDEO, IMAGE}) + public @interface TAG {} + + public static final int TEXT_VIEW = 1; // for normal tag can be formatted by text view + public static final int TABLE_VIEW = 2; // for table tag + public static final int VIDEO_VIEW = 3; // for video tag + public static final int IMAGE_VIEW = 4; // for img tag + + @IntDef({TEXT_VIEW, TABLE_VIEW, VIDEO_VIEW, IMAGE_VIEW}) + public @interface ViewType {} + + @ViewType public int type; + public String info; + + private static Map sSupportTagMap = new HashMap<>(); + + /** + * All support tags parse need be mapped with extra native view,and register here for remarking + */ + static { + sSupportTagMap.put(TABLE, TABLE_VIEW); + sSupportTagMap.put(IMAGE, IMAGE_VIEW); + } + + private static HtmlComponent getComponent(@ViewType int type, String info) { + HtmlComponent htmlComponent = new HtmlComponent(); + htmlComponent.type = type; + htmlComponent.info = info; + return htmlComponent; + } + + private static Matcher matcher(String originHtml, @TAG String tagName) { + Pattern pattern = Pattern.compile(regexByTagName(tagName)); + return pattern.matcher(originHtml); + } + + private static String regexByTagName(String tagName) { + String regex; + if (tagName.endsWith(TABLE)) { + regex = TABLE_REGEX.replace("tag", tagName).replace("TAG", tagName.toUpperCase()); + } else { + regex = SINGLE_TAG_REGEX.replace("tag", tagName).replace("TAG", tagName.toUpperCase()); + } + return regex; + } + + private static ArrayList getInnerComponents( + String originHtml, @TAG String tagName) { + ArrayList htmlComponents = new ArrayList<>(); + // trim useless symbols in case failed regex + originHtml = originHtml.replaceAll("[\\t\\n\\r]", ""); + Matcher tagMatcher = matcher(originHtml, tagName); + String[] tagSplit = originHtml.split(regexByTagName(tagName)); + if (tagSplit.length == 1) { + htmlComponents.add(HtmlComponent.getComponent(HtmlComponent.TEXT_VIEW, originHtml)); + } else { + int index = 0; + while (tagMatcher.find()) { + htmlComponents.add(HtmlComponent.getComponent(HtmlComponent.TEXT_VIEW, tagSplit[index])); + htmlComponents.add( + HtmlComponent.getComponent(sSupportTagMap.get(tagName), tagMatcher.group())); + WXLogUtils.e( + "find " + tagName + " -> start:" + tagMatcher.start() + " end:" + tagMatcher.end()); + index++; + } + htmlComponents.add(HtmlComponent.getComponent(HtmlComponent.TEXT_VIEW, tagSplit[index])); + } + return htmlComponents; + } + + /** + * Wrap tag with standard html than contains css style + * make
looks more artistic + * @param info
string + * @return whole html template + */ + public static String getHtmlTemplateForTable(String template, String info) { + + String temp; + if (TextUtils.isEmpty(template) || !template.contains("placeholder")) { + temp = HTML_TEMPLATE; + WXLogUtils.e( + "Custom template must contains 'placeholder' for table,See " + + "HtmlComponent#HTML_TEMPLATE"); + }else { + temp = template; + } + return temp.replace("placeholder", info); + } + + /** + * Pick up html tag's specified attribute's value,looks like: "src" from + * + * @param keyName attribute's name + * @param info specified string hold tags + * @return the value + */ + public static String getAttributeValue(String keyName, String info) { + String regex = ATTRIBUTE_REGEX.replace("key", keyName).replace("KEY", keyName.toUpperCase()); + Matcher matcher = Pattern.compile(regex).matcher(info); + if (matcher.find()) { + // the third group with parenthesis + return matcher.group(3); + } + return ""; + } + + /** + * Parse html tags that need be implemented by android view, we need two steps: 1、custom tag info, + * remark by {@link TAG} 2、custom native view, remarks by {@link ViewType} + * + * @param originHtml the formatted html + * @param tagNames tags will support + * @return the {@link HtmlComponent} list use by native view + */ + public static List parseTags(String originHtml, @TAG String... tagNames) { + originHtml = originHtml.replaceAll("\\\\", ""); + CopyOnWriteArrayList htmlComponents = new CopyOnWriteArrayList<>(); + // make sure, table tag must be the first one to be parsed, in case
can hold + // any other tags inside. + List allTagNames = new ArrayList<>(Arrays.asList(tagNames)); + if (allTagNames.size() != 0 && allTagNames.contains(TABLE)) { + allTagNames.remove(TABLE); + allTagNames.add(0, TABLE); + } + for (String tagName : allTagNames) { + // check rationality + if (!sSupportTagMap.containsKey(tagName)) { + throw new IllegalArgumentException(tagName + " tag is not supported now!"); + } + if (htmlComponents.size() == 0) { // parse first tag. named
.. + htmlComponents.addAll(getInnerComponents(originHtml, tagName)); + } else { + for (HtmlComponent htmlComponent : htmlComponents) { + if (htmlComponent.type == TEXT_VIEW) { // only text-view type can be spitted again + ArrayList innerHtmlComponents = + getInnerComponents(htmlComponent.info, tagName); + if (innerHtmlComponents.size() == 1) { // don't find the tag in this text-view info + // Do Nothing + } else { // found & replace the old one, with separate multiple components + int replaceIndex = htmlComponents.indexOf(htmlComponent); + htmlComponents.remove(replaceIndex); + htmlComponents.addAll(replaceIndex, innerHtmlComponents); + } + } + } + } + } + return htmlComponents; + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/JellyBeanSpanFixTextView.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/JellyBeanSpanFixTextView.java new file mode 100644 index 0000000000..e07cd57388 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/JellyBeanSpanFixTextView.java @@ -0,0 +1,211 @@ +/* + * 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 com.taobao.weex.ui.component.html; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.util.AttributeSet; + +import com.taobao.weex.utils.WXLogUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Bruce Too + * On 2019/1/9. + * At 19:52 + * *

+ * * A {@link android.widget.TextView} that insert spaces around its text spans where needed to prevent + * * {@link IndexOutOfBoundsException} in {@link #onMeasure(int, int)} on Jelly Bean. + * *

+ * * When {@link #onMeasure(int, int)} throws an exception, we try to fix the text by adding spaces + * * around spans, until it works again. We then try removing some of the added spans, to minimize the + * * insertions. + * *

+ * * The fix is time consuming (a few ms, it depends on the size of your text), but it should only + * * happen once per text change. + * *

+ * * See http://code.google.com/p/android/issues/detail?id=35466 + * *

+ * * From https://gist.github.com/pyricau/3424004 with fix from comments + */ +public class JellyBeanSpanFixTextView extends android.support.v7.widget.AppCompatTextView { + + public static final String TAG = JellyBeanSpanFixTextView.class.getSimpleName(); + + private static class FixingResult { + public final boolean fixed; + public final List spansWithSpacesBefore; + public final List spansWithSpacesAfter; + + public static FixingResult fixed(List spansWithSpacesBefore, + List spansWithSpacesAfter) { + return new FixingResult(true, spansWithSpacesBefore, spansWithSpacesAfter); + } + + public static FixingResult notFixed() { + return new FixingResult(false, null, null); + } + + private FixingResult(boolean fixed, List spansWithSpacesBefore, + List spansWithSpacesAfter) { + this.fixed = fixed; + this.spansWithSpacesBefore = spansWithSpacesBefore; + this.spansWithSpacesAfter = spansWithSpacesAfter; + } + } + + public JellyBeanSpanFixTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public JellyBeanSpanFixTextView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public JellyBeanSpanFixTextView(Context context) { + super(context); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + try { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } catch (IndexOutOfBoundsException e) { + fixOnMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + /** + * If possible, fixes the Spanned text by adding spaces around spans when needed. + */ + private void fixOnMeasure(int widthMeasureSpec, int heightMeasureSpec) { + CharSequence text = getText(); + if (text instanceof Spanned) { + SpannableStringBuilder builder = new SpannableStringBuilder(text); + fixSpannedWithSpaces(builder, widthMeasureSpec, heightMeasureSpec); + } else { + WXLogUtils.d(TAG, "The text isn't a Spanned"); + fallbackToString(widthMeasureSpec, heightMeasureSpec); + } + } + + /** + * Add spaces around spans until the text is fixed, and then removes the unneeded spaces + */ + private void fixSpannedWithSpaces(SpannableStringBuilder builder, int widthMeasureSpec, + int heightMeasureSpec) { + long startFix = System.currentTimeMillis(); + + FixingResult result = addSpacesAroundSpansUntilFixed(builder, widthMeasureSpec, + heightMeasureSpec); + + if (result.fixed) { + removeUnneededSpaces(widthMeasureSpec, heightMeasureSpec, builder, result); + } else { + fallbackToString(widthMeasureSpec, heightMeasureSpec); + } + + long fixDuration = System.currentTimeMillis() - startFix; + WXLogUtils.d(TAG, "fixSpannedWithSpaces() duration in ms: " + fixDuration); + } + + private FixingResult addSpacesAroundSpansUntilFixed(SpannableStringBuilder builder, + int widthMeasureSpec, int heightMeasureSpec) { + + Object[] spans = builder.getSpans(0, builder.length(), Object.class); + List spansWithSpacesBefore = new ArrayList<>(spans.length); + List spansWithSpacesAfter = new ArrayList<>(spans.length); + + for (Object span : spans) { + int spanStart = builder.getSpanStart(span); + if (isNotSpace(builder, spanStart - 1)) { + builder.insert(spanStart, " "); + spansWithSpacesBefore.add(span); + } + + int spanEnd = builder.getSpanEnd(span); + if (isNotSpace(builder, spanEnd)) { + builder.insert(spanEnd, " "); + spansWithSpacesAfter.add(span); + } + + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + return FixingResult.fixed(spansWithSpacesBefore, spansWithSpacesAfter); + } catch (IndexOutOfBoundsException ignored) { + } + } + WXLogUtils.d(TAG, "Could not fix the Spanned by adding spaces around spans"); + return FixingResult.notFixed(); + } + + private boolean isNotSpace(CharSequence text, int where) { + return where < 0 || where >= text.length() || text.charAt(where) != ' '; + } + + @SuppressLint("WrongCall") + private void setTextAndMeasure(CharSequence text, int widthMeasureSpec, int heightMeasureSpec) { + setText(text); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + + @SuppressLint("WrongCall") + private void removeUnneededSpaces(int widthMeasureSpec, int heightMeasureSpec, + SpannableStringBuilder builder, FixingResult result) { + + for (Object span : result.spansWithSpacesAfter) { + int spanEnd = builder.getSpanEnd(span); + builder.delete(spanEnd, spanEnd + 1); + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + } catch (IndexOutOfBoundsException ignored) { + builder.insert(spanEnd, " "); + } + } + + boolean needReset = true; + for (Object span : result.spansWithSpacesBefore) { + int spanStart = builder.getSpanStart(span); + builder.delete(spanStart - 1, spanStart); + try { + setTextAndMeasure(builder, widthMeasureSpec, heightMeasureSpec); + needReset = false; + } catch (IndexOutOfBoundsException ignored) { + needReset = true; + int newSpanStart = spanStart - 1; + builder.insert(newSpanStart, " "); + } + } + + if (needReset) { + setText(builder); + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + private void fallbackToString(int widthMeasureSpec, int heightMeasureSpec) { + WXLogUtils.d(TAG, "Fallback to unspanned text"); + String fallbackText = getText().toString(); + setTextAndMeasure(fallbackText, widthMeasureSpec, heightMeasureSpec); + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/NumberSpan.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/NumberSpan.java new file mode 100644 index 0000000000..4a6da3f78b --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/NumberSpan.java @@ -0,0 +1,108 @@ +/* + * 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 com.taobao.weex.ui.component.html; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.os.Parcel; +import android.text.Layout; +import android.text.ParcelableSpan; +import android.text.Spanned; +import android.text.style.LeadingMarginSpan; + +/** + * Created by Bruce Too + * On 2019/1/9. + * At 19:41 + * Class to use Numbered Lists in TextViews. + * The span works the same as {@link android.text.style.BulletSpan} and all lines of the entry have + * the same leading margin. + */ +public class NumberSpan implements LeadingMarginSpan, ParcelableSpan { + private final int mGapWidth; + private final String mNumber; + + public static final int STANDARD_GAP_WIDTH = 10; + + public NumberSpan(int gapWidth, int number) { + mGapWidth = gapWidth; + mNumber = Integer.toString(number).concat("."); + } + + public NumberSpan(int number) { + mGapWidth = STANDARD_GAP_WIDTH; + mNumber = Integer.toString(number).concat("."); + } + + public NumberSpan(Parcel src) { + mGapWidth = src.readInt(); + mNumber = src.readString(); + } + + public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + /** + * @hide + */ + public int getSpanTypeIdInternal() { + return 8; + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + writeToParcelInternal(dest, flags); + } + + /** + * @hide + */ + public void writeToParcelInternal(Parcel dest, int flags) { + dest.writeInt(mGapWidth); + } + + public int getLeadingMargin(boolean first) { + return 2 * STANDARD_GAP_WIDTH + mGapWidth; + } + + public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, + int top, int baseline, int bottom, + CharSequence text, int start, int end, + boolean first, Layout l) { + if (((Spanned) text).getSpanStart(this) == start) { + Paint.Style style = p.getStyle(); + + p.setStyle(Paint.Style.FILL); + + if (c.isHardwareAccelerated()) { + c.save(); + c.drawText(mNumber, x + dir, baseline, p); + c.restore(); + } else { + c.drawText(mNumber, x + dir, (top + bottom) / 2.0f, p); + } + + p.setStyle(style); + } + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlComponent.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlComponent.java new file mode 100644 index 0000000000..d128209e6c --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlComponent.java @@ -0,0 +1,183 @@ +/** + * 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 com.taobao.weex.ui.component.html; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.alibaba.fastjson.JSONObject; +import com.taobao.weex.WXSDKInstance; +import com.taobao.weex.common.Constants; +import com.taobao.weex.dom.CSSShorthand; +import com.taobao.weex.dom.binding.JSONUtils; +import com.taobao.weex.ui.action.BasicComponentData; +import com.taobao.weex.ui.component.WXComponent; +import com.taobao.weex.ui.component.WXComponentProp; +import com.taobao.weex.ui.component.WXVContainer; +import com.taobao.weex.utils.WXLogUtils; +import com.taobao.weex.utils.WXUtils; +import com.taobao.weex.utils.WXViewUtils; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Bruce Too + * On 2019/1/5. + * At 10:51 + * A Component that handle unsupported tags by custom native view + * Util now, we use {@link TextView} to map normal text-string, support + *
    • in {@link TextView} (Not handle nested li now...WIP) + * And can extend {@link WxHtmlTagHandler} to support more tag + * + * ImageView to map tag + * WebView to map

tag with custom wrap html template {@link HtmlComponent#HTML_TEMPLATE} + */ +public class WxHtmlComponent extends WXComponent { + + private static final int DEFAULT_PAGE_EDGE = 20; + private List mHtmlComponents = new ArrayList<>(); + private String mImageResize = "cover"; + private String mTableTemplate = HtmlComponent.HTML_TEMPLATE; + private int mLeftEdge = DEFAULT_PAGE_EDGE; + private int mRightEdge = DEFAULT_PAGE_EDGE; + + public WxHtmlComponent( + WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) { + super(instance, parent, basicComponentData); + } + + public WxHtmlComponent( + WXSDKInstance instance, + WXVContainer parent, + int type, + BasicComponentData basicComponentData) { + super(instance, parent, type, basicComponentData); + } + + @Override + protected ScrollView initComponentHostView(@NonNull final Context context) { + LinearLayout layout = new LinearLayout(context); + layout.setOrientation(LinearLayout.VERTICAL); + ScrollView scrollView = new ScrollView(context); + scrollView.addView( + layout, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + return scrollView; + } + + /** + * image: { resize: 'cover' }, table: { template: "" } template related to {@link + * HtmlComponent#HTML_TEMPLATE} + * + * @param htmlOption options for html + */ + @WXComponentProp(name = "htmlOption") + public void setOption(String htmlOption) { + extractOption(htmlOption); + } + + public void extractOption(String htmlOption) { + try { + JSONObject option = JSONUtils.toJSON(htmlOption); + if (option != null) { + // image + JSONObject image = option.getJSONObject("image"); + mImageResize = WXUtils.getString(image.get(Constants.Name.RESIZE), "cover"); + // table + mTableTemplate = WXUtils.getString(image.get("template"), HtmlComponent.HTML_TEMPLATE); + } + + CSSShorthand padding = getBasicComponentData().getPadding(); + CSSShorthand margin = getBasicComponentData().getMargin(); + mLeftEdge = + (int) + (padding.get(CSSShorthand.EDGE.LEFT) + + padding.get(CSSShorthand.EDGE.ALL) + + margin.get(CSSShorthand.EDGE.LEFT) + + margin.get(CSSShorthand.EDGE.ALL)); + + mRightEdge = + (int) + (padding.get(CSSShorthand.EDGE.RIGHT) + + padding.get(CSSShorthand.EDGE.ALL) + + margin.get(CSSShorthand.EDGE.RIGHT) + + margin.get(CSSShorthand.EDGE.ALL)); + + mLeftEdge = mLeftEdge == 0 ? WXViewUtils.dip2px(DEFAULT_PAGE_EDGE) : mLeftEdge; + mRightEdge = mRightEdge == 0 ? WXViewUtils.dip2px(DEFAULT_PAGE_EDGE) : mRightEdge; + } catch (Exception e) { + WXLogUtils.e("extractOption failed:" + e.getMessage()); + } + } + + @WXComponentProp(name = "htmlText") + public void setHtml(String htmlText) { + + extractOption(WXUtils.getString(getAttrs().get("htmlOption"), "")); + + ViewGroup layout = (ViewGroup) getHostView().getChildAt(0); + if (layout.getChildCount() != 0) { + layout.removeAllViews(); + } + + if (mHtmlComponents.size() == 0) { + mHtmlComponents.addAll( + HtmlComponent.parseTags(htmlText, HtmlComponent.IMAGE, HtmlComponent.TABLE)); + } + + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + for (HtmlComponent htmlComponent : mHtmlComponents) { + layout.addView( + getInstance() + .getHtmlTextAdapter() + .getHtmlTagView(getContext(), this, htmlComponent.type, htmlComponent.info), + params); + } + } + + public String getImageResize() { + return mImageResize; + } + + public String getTableTemplate() { + return mTableTemplate; + } + + /** + * left/right params,use to get the real view width in screen + * + * @return width of edges(left/right) + */ + public int getEdgesWidth() { + return mLeftEdge + mRightEdge; + } + + @Override + public void destroy() { + super.destroy(); + mHtmlComponents.clear(); + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlTagHandler.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlTagHandler.java new file mode 100644 index 0000000000..b2c09b506c --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlTagHandler.java @@ -0,0 +1,67 @@ +/* + * 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 com.taobao.weex.ui.component.html; + +import android.text.Editable; +import android.text.Spannable; + +import com.taobao.weex.ui.component.html.htmlcompat.HtmlCompat; + +import org.xml.sax.Attributes; +import org.xml.sax.XMLReader; +/** + * Created by Bruce Too On 2019/1/9. At 19:31 + */ +public class WxHtmlTagHandler implements HtmlCompat.TagHandler { + + private static final String TAG = WxHtmlTagHandler.class.getSimpleName(); + + @Override + public void handleTag(boolean opening, String tag, Attributes attributes, Editable output, XMLReader xmlReader) { + + } + + /** Returns the text contained within a span and deletes it from the output string */ + private CharSequence extractSpanText(Editable output, Class kind) { + final Object obj = getLast(output, kind); + // start of the tag + final int where = output.getSpanStart(obj); + // end of the tag + final int len = output.length(); + + final CharSequence extractedSpanText = output.subSequence(where, len); + output.delete(where, len); + return extractedSpanText; + } + + /** Get last marked position of a specific tag kind (private class) */ + private static Object getLast(Editable text, Class kind) { + Object[] objs = text.getSpans(0, text.length(), kind); + if (objs.length == 0) { + return null; + } else { + for (int i = objs.length; i > 0; i--) { + if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) { + return objs[i - 1]; + } + } + return null; + } + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/adapter/DefaultHtmlTagAdapter.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/adapter/DefaultHtmlTagAdapter.java new file mode 100644 index 0000000000..97154fecf7 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/adapter/DefaultHtmlTagAdapter.java @@ -0,0 +1,232 @@ +/** + * 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 com.taobao.weex.ui.component.html.adapter; + +import android.content.Context; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.TextUtils; +import android.text.method.LinkMovementMethod; +import android.text.style.ClickableSpan; +import android.text.style.URLSpan; +import android.view.View; +import android.webkit.WebView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Toast; + +import com.taobao.weex.WXEnvironment; +import com.taobao.weex.WXSDKManager; +import com.taobao.weex.adapter.IWxHtmlTagAdapter; +import com.taobao.weex.ui.component.html.AtMostWebView; +import com.taobao.weex.ui.component.html.HtmlComponent; +import com.taobao.weex.ui.component.html.JellyBeanSpanFixTextView; +import com.taobao.weex.ui.component.html.WxHtmlComponent; +import com.taobao.weex.ui.component.html.WxHtmlTagHandler; +import com.taobao.weex.ui.component.html.htmlcompat.HtmlCompat; +import com.taobao.weex.utils.WXViewUtils; + +import java.util.HashMap; +import java.util.Map; + +/** Created by Bruce Too On 2019/1/10. At 09:56 */ +public class DefaultHtmlTagAdapter implements IWxHtmlTagAdapter { + + protected static final float DEFAULT_IMAGE_ASPECT_RATIO = 0.75f; // 3 * 1.0f/ 4; + protected Context context; + protected WxHtmlComponent component; + /** All wrapped native views with there "src" value */ + protected Map mImageMap = new HashMap<>(); + + @Override + public View getHtmlTagView(Context context, WxHtmlComponent component, int tagType, String html) { + this.context = context; + this.component = component; + View tagView = null; + switch (tagType) { + case HtmlComponent.TEXT_VIEW: + tagView = getDefaultTextView(html); + break; + case HtmlComponent.TABLE_VIEW: + tagView = getDefaultTabView(html); + break; + case HtmlComponent.IMAGE_VIEW: + tagView = getDefaultImageView(html); + break; + case HtmlComponent.VIDEO_VIEW: + default: // text + View extendTagView = getExtendTagView(html); + tagView = extendTagView == null ? getDefaultTextView(html) : extendTagView; + break; + } + return tagView; + } + + @Override + public View.OnClickListener getTagViewClickListener(int tagType, String html) { + // View.OnClickListener onClickListener = null; + // switch (tagType) { + // case HtmlComponent.TEXT_VIEW: + // break; + // case HtmlComponent.TABLE_VIEW: + // break; + // case HtmlComponent.IMAGE_VIEW: + // onClickListener = new EmptyClickListener(); + // break; + // case HtmlComponent.VIDEO_VIEW: + // onClickListener = new EmptyClickListener(); + // break; + // default: // text + // break; + // } + return new EmptyClickListener(); + } + + /** + * For custom view + * + * @param info html string + * @return tag's native view + */ + protected View getExtendTagView(String info) { + return null; + } + + protected WebView getDefaultTabView(String info) { + AtMostWebView webView = new AtMostWebView(context); + webView.setOnLongClickListener( + new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + return true; + } + }); + webView.loadDataWithBaseURL( + null, + HtmlComponent.getHtmlTemplateForTable(component.getTableTemplate(), info), + "text" + "/html", + "utf-8", + null); + return webView; + } + + protected ImageView getDefaultImageView(String info) { + ImageView imageView = new ImageView(context); + imageView.setScaleType(getResizeMode(component.getImageResize())); + int hegiht = -1; + int width = -1; + try { + // [a-zA-Z]{2}$ for unit of px or wx + hegiht = + Integer.valueOf( + HtmlComponent.getAttributeValue("height", info).replaceAll("[a-zA-Z]{2}$", "")); + width = + Integer.valueOf( + HtmlComponent.getAttributeValue("width", info).replaceAll("[a-zA-Z]{2}$", "")); + } catch (Exception e) { + // Not the case we care now,ignore + } + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + params.width = WXViewUtils.getScreenWidth(context) - component.getEdgesWidth(); + if (hegiht == -1 || width == -1) { // don't have explicit height or width + params.height = (int) (params.width * DEFAULT_IMAGE_ASPECT_RATIO); + } else { + params.height = (int) (params.width * hegiht * 1.0f / width); + } + imageView.setLayoutParams(params); + String src = HtmlComponent.getAttributeValue("src", info); + WXSDKManager.getInstance().getIWXImgLoaderAdapter().setImage(src, imageView, null, null); + + imageView.setOnClickListener(getTagViewClickListener(HtmlComponent.IMAGE_VIEW, info)); + mImageMap.put(imageView, src); + return imageView; + } + + protected View getDefaultTextView(String html) { + JellyBeanSpanFixTextView textView = new JellyBeanSpanFixTextView(context); + WxHtmlTagHandler tagHandler = new WxHtmlTagHandler(); + textView.setText( + HtmlCompat.fromHtml(context, html, HtmlCompat.FROM_HTML_MODE_LEGACY, null, tagHandler)); + + textView.setMovementMethod(LinkMovementMethod.getInstance()); + CharSequence text = textView.getText(); + if (text instanceof Spannable) { + Spannable sp = (Spannable) text; + URLSpan[] oldUrlSpans = sp.getSpans(0, text.length(), URLSpan.class); + SpannableStringBuilder ssb = new SpannableStringBuilder(text); + for (URLSpan oldUrlSpan : oldUrlSpans) { + // ssb.removeSpan(oldUrlSpan); + DefaultURLSpan defaultURLSpan = new DefaultURLSpan(oldUrlSpan.getURL()); + ssb.setSpan( + defaultURLSpan, + sp.getSpanStart(oldUrlSpan), + sp.getSpanEnd(oldUrlSpan), + Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + textView.setText(ssb); + } + return textView; + } + + private class DefaultURLSpan extends ClickableSpan { + private String url; + + DefaultURLSpan(String url) { + this.url = url; + } + + @Override + public void onClick(View view) { + getTagViewClickListener(HtmlComponent.TEXT_VIEW, url).onClick(view); + } + } + + private ImageView.ScaleType getResizeMode(String resizeMode) { + ImageView.ScaleType scaleType = ImageView.ScaleType.FIT_XY; + if (TextUtils.isEmpty(resizeMode)) { + return scaleType; + } + + switch (resizeMode) { + case "cover": + scaleType = ImageView.ScaleType.CENTER_CROP; + break; + case "contain": + scaleType = ImageView.ScaleType.FIT_CENTER; + break; + case "stretch": + scaleType = ImageView.ScaleType.FIT_XY; + break; + default: + break; + } + return scaleType; + } + + private class EmptyClickListener implements View.OnClickListener { + @Override + public void onClick(View v) { + if (WXEnvironment.isApkDebugable()) { + Toast.makeText(context, "you click the view", Toast.LENGTH_SHORT).show(); + } + } + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/ColorUtils.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/ColorUtils.java new file mode 100755 index 0000000000..b4a25ce82c --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/ColorUtils.java @@ -0,0 +1,88 @@ +/** + * 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 com.taobao.weex.ui.component.html.htmlcompat; + +import android.support.annotation.ColorInt; + +import java.util.HashMap; +import java.util.Locale; + +import static android.graphics.Color.BLACK; +import static android.graphics.Color.BLUE; +import static android.graphics.Color.CYAN; +import static android.graphics.Color.DKGRAY; +import static android.graphics.Color.GRAY; +import static android.graphics.Color.GREEN; +import static android.graphics.Color.LTGRAY; +import static android.graphics.Color.MAGENTA; +import static android.graphics.Color.RED; +import static android.graphics.Color.WHITE; +import static android.graphics.Color.YELLOW; + +class ColorUtils { + + /** + * Converts an HTML color (named or numeric) to an integer RGB value. + * + * @param color Non-null color string. + * + * @return A color value, or {@code -1} if the color string could not be interpreted. + */ + @ColorInt + public static int getHtmlColor(String color) { + Integer i = sColorNameMap.get(color.toLowerCase(Locale.ROOT)); + if (i != null) { + return i; + } else { + try { + return XmlUtils.convertValueToInt(color, -1); + } catch (NumberFormatException nfe) { + return -1; + } + } + } + private static final HashMap sColorNameMap; + static { + sColorNameMap = new HashMap(); + sColorNameMap.put("black", BLACK); + sColorNameMap.put("darkgray", DKGRAY); + sColorNameMap.put("gray", GRAY); + sColorNameMap.put("lightgray", LTGRAY); + sColorNameMap.put("white", WHITE); + sColorNameMap.put("red", RED); + sColorNameMap.put("green", GREEN); + sColorNameMap.put("blue", BLUE); + sColorNameMap.put("yellow", YELLOW); + sColorNameMap.put("cyan", CYAN); + sColorNameMap.put("magenta", MAGENTA); + sColorNameMap.put("aqua", 0xFF00FFFF); + sColorNameMap.put("fuchsia", 0xFFFF00FF); + sColorNameMap.put("darkgrey", DKGRAY); + sColorNameMap.put("grey", GRAY); + sColorNameMap.put("lightgrey", LTGRAY); + sColorNameMap.put("lime", 0xFF00FF00); + sColorNameMap.put("maroon", 0xFF800000); + sColorNameMap.put("navy", 0xFF000080); + sColorNameMap.put("olive", 0xFF808000); + sColorNameMap.put("purple", 0xFF800080); + sColorNameMap.put("silver", 0xFFC0C0C0); + sColorNameMap.put("teal", 0xFF008080); + } + +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlCompat.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlCompat.java new file mode 100755 index 0000000000..d061ea51a2 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlCompat.java @@ -0,0 +1,665 @@ +/** + * 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 com.taobao.weex.ui.component.html.htmlcompat; + +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed 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. + */ + +import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.Editable; +import android.text.Layout; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.BulletSpan; +import android.text.style.CharacterStyle; +import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; +import android.text.style.ParagraphStyle; +import android.text.style.QuoteSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.SubscriptSpan; +import android.text.style.SuperscriptSpan; +import android.text.style.TypefaceSpan; +import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; +import android.view.View; + +import org.ccil.cowan.tagsoup.HTMLSchema; +import org.ccil.cowan.tagsoup.Parser; +import org.xml.sax.Attributes; +import org.xml.sax.XMLReader; + +/** + * This class processes HTML strings into displayable styled text. + * Not all HTML tags are supported. + */ +@SuppressWarnings({"unused", "WeakerAccess"}) +public class HtmlCompat { + + /** + * Retrieves images for HTML <img> tags. + */ + public interface ImageGetter { + /** + * This method is called when the HTML parser encounters an + * <img> tag. The source argument is the + * string from the "src" attribute; the return value should be + * a Drawable representation of the image or null + * for a generic replacement image. Make sure you call + * setBounds() on your Drawable if it doesn't already have + * its bounds set. + */ + Drawable getDrawable(String source, Attributes attributes); + } + + /** + * Is notified when HTML tags are encountered that the parser does + * not know how to interpret. + */ + public interface TagHandler { + /** + * This method will be called when the HTML parser encounters + * a tag that it does not know how to interpret. + */ + void handleTag(boolean opening, String tag, + Attributes attributes, Editable output, XMLReader xmlReader); + } + + /** + * Is notified when a new span is created. + */ + public interface SpanCallback { + /** + * This method will be called when a new span is created. + */ + Object onSpanCreated(String tag, Object span); + } + + @SuppressLint("ParcelCreator") + public static class DefensiveURLSpan extends URLSpan { + + public DefensiveURLSpan(String url) { + super(url); + } + + @Override + public void onClick(View widget) { + try { + super.onClick(widget); + } catch (ActivityNotFoundException e) { + // Ignore + } + } + + } + + /** + * Option for {@link #toHtml(Context, Spanned, int)}: Wrap consecutive lines of text delimited by '\n' + * inside <p> elements. {@link BulletSpan}s are ignored. + */ + public static final int TO_HTML_PARAGRAPH_LINES_CONSECUTIVE = 0x00000000; + /** + * Option for {@link #toHtml(Context, Spanned, int)}: Wrap each line of text delimited by '\n' inside a + * <p> or a <li> element. This allows {@link ParagraphStyle}s attached to be + * encoded as CSS styles within the corresponding <p> or <li> element. + */ + public static final int TO_HTML_PARAGRAPH_LINES_INDIVIDUAL = 0x00000001; + /** + * Flag indicating that texts inside <p> elements will be separated from other texts with + * one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH = 0x00000001; + /** + * Flag indicating that texts inside <h1>~<h6> elements will be separated from + * other texts with one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_HEADING = 0x00000002; + /** + * Flag indicating that texts inside <li> elements will be separated from other texts + * with one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM = 0x00000004; + /** + * Flag indicating that texts inside <ul> elements will be separated from other texts + * with one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_LIST = 0x00000008; + /** + * Flag indicating that texts inside <div> elements will be separated from other texts + * with one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_DIV = 0x00000010; + /** + * Flag indicating that texts inside <blockquote> elements will be separated from other + * texts with one newline character by default. + */ + public static final int FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE = 0x00000020; + /** + * Flag indicating that CSS color values should be used instead of those defined in + * {@link Color}. + */ + public static final int FROM_HTML_OPTION_USE_CSS_COLORS = 0x00000100; + /** + * Flags for {@link #fromHtml(Context, String, int, ImageGetter, TagHandler)}: Separate block-level + * elements with blank lines (two newline characters) in between. This is the legacy behavior + * prior to N. + */ + public static final int FROM_HTML_MODE_LEGACY = 0x00000000; + /** + * Flags for {@link #fromHtml(Context, String, int, ImageGetter, TagHandler)}: Separate block-level + * elements with line breaks (single newline character) in between. This inverts the + * {@link Spanned} to HTML string conversion done with the option + * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}. + */ + public static final int FROM_HTML_MODE_COMPACT = + FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH + | FROM_HTML_SEPARATOR_LINE_BREAK_HEADING + | FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM + | FROM_HTML_SEPARATOR_LINE_BREAK_LIST + | FROM_HTML_SEPARATOR_LINE_BREAK_DIV + | FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE; + /** + * The bit which indicates if lines delimited by '\n' will be grouped into <p> elements. + */ + private static final int TO_HTML_PARAGRAPH_FLAG = 0x00000001; + + private HtmlCompat() { + } + + /** + * Returns displayable styled text from the provided HTML string. Any <img> tags in the + * HTML will display as a generic replacement image which your program can then go through and + * replace with real images. + *

+ *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. + */ + public static Spanned fromHtml(@NonNull Context context, @NonNull String source, int flags) { + return fromHtml(context, source, flags, null, null); + } + + /** + * Lazy initialization holder for HTML parser. This class will + * a) be preloaded by the zygote, or b) not loaded until absolutely + * necessary. + */ + private static class HtmlParser { + private static final HTMLSchema schema = new HTMLSchema(); + } + + + /** + * Returns displayable styled text from the provided HTML string. Any <img> tags in the + * HTML will not use an ImageGetter to request a representation of the image or TagHandler to + * handle unknown tags. + *

+ *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. + */ + public static Spanned fromHtml(@NonNull Context context, @NonNull String source, int flags, + @Nullable ImageGetter imageGetter) { + return fromHtml(context, source, flags, imageGetter, null, null); + } + + /** + * Returns displayable styled text from the provided HTML string. Any <img> tags in the + * HTML will use the specified ImageGetter to request a representation of the image (use null + * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if + * you don't want this). + *

+ *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. + */ + public static Spanned fromHtml(@NonNull Context context, @NonNull String source, int flags, + @Nullable ImageGetter imageGetter, @Nullable TagHandler tagHandler) { + return fromHtml(context, source, flags, imageGetter, tagHandler, null); + } + + /** + * Returns displayable styled text from the provided HTML string. Any <img> tags in the + * HTML will use the specified ImageGetter to request a representation of the image (use null + * if you don't want this) and the specified TagHandler to handle unknown tags (specify null if + * you don't want this). + *

+ *

This uses TagSoup to handle real HTML, including all of the brokenness found in the wild. + */ + public static Spanned fromHtml(@NonNull Context context, @NonNull String source, int flags, + @Nullable ImageGetter imageGetter, @Nullable TagHandler tagHandler, + @Nullable SpanCallback spanCallback) { + if (source == null) { + return null; + } + Parser parser = new Parser(); + try { + parser.setProperty(Parser.schemaProperty, HtmlParser.schema); + } catch (org.xml.sax.SAXNotRecognizedException | org.xml.sax.SAXNotSupportedException e) { + // Should not happen. + throw new RuntimeException(e); + } + HtmlToSpannedConverter converter = + new HtmlToSpannedConverter(context, source, imageGetter, tagHandler, spanCallback, parser, flags); + return converter.convert(); + } + + /** + * Returns an HTML representation of the provided Spanned text. A best effort is + * made to add HTML tags corresponding to spans. Also note that HTML metacharacters + * (such as "<" and "&") within the input text are escaped. + * + * @param text input text to convert + * @param option one of {@link #TO_HTML_PARAGRAPH_LINES_CONSECUTIVE} or + * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL} + * @return string containing input converted to HTML + */ + public static String toHtml(Context context, Spanned text, int option) { + StringBuilder out = new StringBuilder(); + withinHtml(context, out, text, option); + return out.toString(); + } + + /** + * Returns an HTML escaped representation of the given plain text. + */ + public static String escapeHtml(CharSequence text) { + StringBuilder out = new StringBuilder(); + withinStyle(out, text, 0, text.length()); + return out.toString(); + } + + private static void withinHtml(Context context, StringBuilder out, Spanned text, int option) { + if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) { + encodeTextAlignmentByDiv(context, out, text, option); + return; + } + withinDiv(context, out, text, 0, text.length(), option); + } + + private static void encodeTextAlignmentByDiv(Context context, StringBuilder out, Spanned text, int option) { + int len = text.length(); + int next; + for (int i = 0; i < len; i = next) { + next = text.nextSpanTransition(i, len, ParagraphStyle.class); + ParagraphStyle[] styles = text.getSpans(i, next, ParagraphStyle.class); + String elements = " "; + boolean needDiv = false; + for (ParagraphStyle style : styles) { + if (style instanceof AlignmentSpan) { + Layout.Alignment align = + ((AlignmentSpan) style).getAlignment(); + needDiv = true; + if (align == Layout.Alignment.ALIGN_CENTER) { + elements = "align=\"center\" " + elements; + } else if (align == Layout.Alignment.ALIGN_OPPOSITE) { + elements = "align=\"right\" " + elements; + } else { + elements = "align=\"left\" " + elements; + } + } + } + if (needDiv) { + out.append("

"); + } + withinDiv(context, out, text, i, next, option); + if (needDiv) { + out.append("
"); + } + } + } + + private static void withinDiv(Context context, StringBuilder out, Spanned text, + int start, int end, int option) { + int next; + for (int i = start; i < end; i = next) { + next = text.nextSpanTransition(i, end, QuoteSpan.class); + QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class); + for (QuoteSpan quote : quotes) { + out.append("
"); + } + withinBlockquote(context, out, text, i, next, option); + for (QuoteSpan quote : quotes) { + out.append("
\n"); + } + } + } + + private static String getTextDirection(Spanned text, int start, int end) { + // FIXME not supported + int paraDir = Layout.DIR_LEFT_TO_RIGHT; +// final int len = end - start; +// final byte[] levels = ArrayUtils.newUnpaddedByteArray(len); +// final char[] buffer = TextUtils.obtain(len); +// TextUtils.getChars(text, start, end, buffer, 0); +// int paraDir = AndroidBidi.bidi(Layout.DIR_REQUEST_DEFAULT_LTR, buffer, levels, len, +// false /* no info */); + switch (paraDir) { + case Layout.DIR_RIGHT_TO_LEFT: + return " dir=\"rtl\""; + case Layout.DIR_LEFT_TO_RIGHT: + default: + return " dir=\"ltr\""; + } + } + + private static String getTextStyles(Spanned text, int start, int end, + boolean forceNoVerticalMargin, boolean includeTextAlign) { + String margin = null; + String textAlign = null; + if (forceNoVerticalMargin) { + margin = "margin-top:0; margin-bottom:0;"; + } + if (includeTextAlign) { + final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class); + // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH + for (int i = alignmentSpans.length - 1; i >= 0; i--) { + AlignmentSpan s = alignmentSpans[i]; + if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) { + final Layout.Alignment alignment = s.getAlignment(); + if (alignment == Layout.Alignment.ALIGN_NORMAL) { + textAlign = "text-align:start;"; + } else if (alignment == Layout.Alignment.ALIGN_CENTER) { + textAlign = "text-align:center;"; + } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) { + textAlign = "text-align:end;"; + } + break; + } + } + } + if (margin == null && textAlign == null) { + return ""; + } + final StringBuilder style = new StringBuilder(" style=\""); + if (margin != null && textAlign != null) { + style.append(margin).append(" ").append(textAlign); + } else if (margin != null) { + style.append(margin); + } else if (textAlign != null) { + style.append(textAlign); + } + return style.append("\"").toString(); + } + + private static void withinBlockquote(Context context, StringBuilder out, Spanned text, + int start, int end, int option) { + if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) { + withinBlockquoteConsecutive(context, out, text, start, end); + } else { + withinBlockquoteIndividual(context, out, text, start, end); + } + } + + private static void withinBlockquoteIndividual(Context context, StringBuilder out, Spanned text, + int start, int end) { + boolean isInList = false; + int next; + for (int i = start; i <= end; i = next) { + next = TextUtils.indexOf(text, '\n', i, end); + if (next < 0) { + next = end; + } + if (next == i) { + if (isInList) { + // Current paragraph is no longer a list item; close the previously opened list + isInList = false; + out.append("\n"); + } + out.append("
\n"); + } else { + boolean isListItem = false; + ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class); + for (ParagraphStyle paragraphStyle : paragraphStyles) { + final int spanFlags = text.getSpanFlags(paragraphStyle); + if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH + && paragraphStyle instanceof BulletSpan) { + isListItem = true; + break; + } + } + if (isListItem && !isInList) { + // Current paragraph is the first item in a list + isInList = true; + out.append("\n"); + } + if (isInList && !isListItem) { + // Current paragraph is no longer a list item; close the previously opened list + isInList = false; + out.append("\n"); + } + String tagType = isListItem ? "li" : "p"; + out.append("<").append(tagType) + .append(getTextDirection(text, i, next)) + .append(getTextStyles(text, i, next, !isListItem, true)) + .append(">"); + withinParagraph(context, out, text, i, next); + out.append("\n"); + if (next == end && isInList) { + isInList = false; + out.append("\n"); + } + } + next++; + } + } + + private static void withinBlockquoteConsecutive(Context context, StringBuilder out, Spanned text, + int start, int end) { + out.append(""); + int next; + for (int i = start; i < end; i = next) { + next = TextUtils.indexOf(text, '\n', i, end); + if (next < 0) { + next = end; + } + int nl = 0; + while (next < end && text.charAt(next) == '\n') { + nl++; + next++; + } + withinParagraph(context, out, text, i, next - nl); + if (nl == 1) { + out.append("
\n"); + } else { + for (int j = 2; j < nl; j++) { + out.append("
"); + } + if (next != end) { + /* Paragraph should be closed and reopened */ + out.append("

\n"); + out.append(""); + } + } + } + out.append("

\n"); + } + + private static void withinParagraph(Context context, StringBuilder out, Spanned text, int start, int end) { + int next; + for (int i = start; i < end; i = next) { + next = text.nextSpanTransition(i, end, CharacterStyle.class); + CharacterStyle[] styles = text.getSpans(i, next, CharacterStyle.class); + for (CharacterStyle style : styles) { + if (style instanceof StyleSpan) { + int s = ((StyleSpan) style).getStyle(); + if ((s & Typeface.BOLD) != 0) { + out.append(""); + } + if ((s & Typeface.ITALIC) != 0) { + out.append(""); + } + } + if (style instanceof TypefaceSpan) { + String s = ((TypefaceSpan) style).getFamily(); + if ("monospace".equals(s)) { + out.append(""); + } + } + if (style instanceof SuperscriptSpan) { + out.append(""); + } + if (style instanceof SubscriptSpan) { + out.append(""); + } + if (style instanceof UnderlineSpan) { + out.append(""); + } + if (style instanceof StrikethroughSpan) { + out.append(""); + } + if (style instanceof URLSpan) { + out.append(""); + } + if (style instanceof ImageSpan) { + out.append(""); + // Don't output the dummy character underlying the image. + i = next; + } + if (style instanceof AbsoluteSizeSpan) { + AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style); + float sizeDip = s.getSize(); + if (!s.getDip()) { + sizeDip /= context.getResources().getDisplayMetrics().density; + } + // px in CSS is the equivalance of dip in Android + out.append(String.format("", sizeDip)); + } + if (style instanceof RelativeSizeSpan) { + float sizeEm = ((RelativeSizeSpan) style).getSizeChange(); + out.append(String.format("", sizeEm)); + } + if (style instanceof ForegroundColorSpan) { + int color = ((ForegroundColorSpan) style).getForegroundColor(); + out.append(String.format("", 0xFFFFFF & color)); + } + if (style instanceof BackgroundColorSpan) { + int color = ((BackgroundColorSpan) style).getBackgroundColor(); + out.append(String.format("", + 0xFFFFFF & color)); + } + } + withinStyle(out, text, i, next); + for (int j = styles.length - 1; j >= 0; j--) { + if (styles[j] instanceof BackgroundColorSpan) { + out.append(""); + } + if (styles[j] instanceof ForegroundColorSpan) { + out.append(""); + } + if (styles[j] instanceof RelativeSizeSpan) { + out.append(""); + } + if (styles[j] instanceof AbsoluteSizeSpan) { + out.append(""); + } + if (styles[j] instanceof URLSpan) { + out.append(""); + } + if (styles[j] instanceof StrikethroughSpan) { + out.append(""); + } + if (styles[j] instanceof UnderlineSpan) { + out.append(""); + } + if (styles[j] instanceof SubscriptSpan) { + out.append(""); + } + if (styles[j] instanceof SuperscriptSpan) { + out.append(""); + } + if (styles[j] instanceof TypefaceSpan) { + String s = ((TypefaceSpan) styles[j]).getFamily(); + if (s.equals("monospace")) { + out.append(""); + } + } + if (styles[j] instanceof StyleSpan) { + int s = ((StyleSpan) styles[j]).getStyle(); + if ((s & Typeface.BOLD) != 0) { + out.append(""); + } + if ((s & Typeface.ITALIC) != 0) { + out.append(""); + } + } + } + } + } + + private static void withinStyle(StringBuilder out, CharSequence text, + int start, int end) { + for (int i = start; i < end; i++) { + char c = text.charAt(i); + if (c == '<') { + out.append("<"); + } else if (c == '>') { + out.append(">"); + } else if (c == '&') { + out.append("&"); + } else if (c >= 0xD800 && c <= 0xDFFF) { + if (c < 0xDC00 && i + 1 < end) { + char d = text.charAt(i + 1); + if (d >= 0xDC00 && d <= 0xDFFF) { + i++; + int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00; + out.append("&#").append(codepoint).append(";"); + } + } + } else if (c > 0x7E || c < ' ') { + out.append("&#").append((int) c).append(";"); + } else if (c == ' ') { + while (i + 1 < end && text.charAt(i + 1) == ' ') { + out.append(" "); + i++; + } + out.append(' '); + } else { + out.append(c); + } + } + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlToSpannedConverter.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlToSpannedConverter.java new file mode 100755 index 0000000000..f7ed600eaa --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlToSpannedConverter.java @@ -0,0 +1,920 @@ +/** + * 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 com.taobao.weex.ui.component.html.htmlcompat; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Color; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.text.Editable; +import android.text.Layout; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.AbsoluteSizeSpan; +import android.text.style.AlignmentSpan; +import android.text.style.BackgroundColorSpan; +import android.text.style.BulletSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; +import android.text.style.ParagraphStyle; +import android.text.style.QuoteSpan; +import android.text.style.RelativeSizeSpan; +import android.text.style.StrikethroughSpan; +import android.text.style.StyleSpan; +import android.text.style.SubscriptSpan; +import android.text.style.SuperscriptSpan; +import android.text.style.TypefaceSpan; +import android.text.style.UnderlineSpan; + +import com.taobao.weex.R; + +import org.ccil.cowan.tagsoup.Parser; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +class HtmlToSpannedConverter implements ContentHandler { + private static final float[] HEADING_SIZES = { + 1.5f, 1.4f, 1.3f, 1.2f, 1.1f, 1f, + }; + private final Context mContext; + private String mSource; + private final HtmlCompat.SpanCallback mSpanCallback; + private XMLReader mReader; + private SpannableStringBuilder mSpannableStringBuilder; + private HtmlCompat.ImageGetter mImageGetter; + private HtmlCompat.TagHandler mTagHandler; + private int mFlags; + private static Pattern sTextAlignPattern; + private static Pattern sForegroundColorPattern; + private static Pattern sBackgroundColorPattern; + private static Pattern sTextDecorationPattern; + private static Pattern sTextFontSizePattern; + /** + * Name-value mapping of HTML/CSS colors which have different values in {@link Color}. + */ + private static final Map sColorMap; + + static { + sColorMap = new HashMap<>(); + sColorMap.put("aliceblue", 0xFFF0F8FF); + sColorMap.put("antiquewhite", 0xFFFAEBD7); + sColorMap.put("aqua", 0xFF00FFFF); + sColorMap.put("aquamarine", 0xFF7FFFD4); + sColorMap.put("azure", 0xFFF0FFFF); + sColorMap.put("beige", 0xFFF5F5DC); + sColorMap.put("bisque", 0xFFFFE4C4); + sColorMap.put("black", 0xFF000000); + sColorMap.put("blanchedalmond", 0xFFFFEBCD); + sColorMap.put("blue", 0xFF0000FF); + sColorMap.put("blueviolet", 0xFF8A2BE2); + sColorMap.put("brown", 0xFFA52A2A); + sColorMap.put("burlywood", 0xFFDEB887); + sColorMap.put("cadetblue", 0xFF5F9EA0); + sColorMap.put("chartreuse", 0xFF7FFF00); + sColorMap.put("chocolate", 0xFFD2691E); + sColorMap.put("coral", 0xFFFF7F50); + sColorMap.put("cornflowerblue", 0xFF6495ED); + sColorMap.put("cornsilk", 0xFFFFF8DC); + sColorMap.put("crimson", 0xFFDC143C); + sColorMap.put("cyan", 0xFF00FFFF); + sColorMap.put("darkblue", 0xFF00008B); + sColorMap.put("darkcyan", 0xFF008B8B); + sColorMap.put("darkgoldenrod", 0xFFB8860B); + sColorMap.put("darkgray", 0xFFA9A9A9); + sColorMap.put("darkgrey", 0xFFA9A9A9); + sColorMap.put("darkgreen", 0xFF006400); + sColorMap.put("darkkhaki", 0xFFBDB76B); + sColorMap.put("darkmagenta", 0xFF8B008B); + sColorMap.put("darkolivegreen", 0xFF556B2F); + sColorMap.put("darkorange", 0xFFFF8C00); + sColorMap.put("darkorchid", 0xFF9932CC); + sColorMap.put("darkred", 0xFF8B0000); + sColorMap.put("darksalmon", 0xFFE9967A); + sColorMap.put("darkseagreen", 0xFF8FBC8F); + sColorMap.put("darkslateblue", 0xFF483D8B); + sColorMap.put("darkslategray", 0xFF2F4F4F); + sColorMap.put("darkslategrey", 0xFF2F4F4F); + sColorMap.put("darkturquoise", 0xFF00CED1); + sColorMap.put("darkviolet", 0xFF9400D3); + sColorMap.put("deeppink", 0xFFFF1493); + sColorMap.put("deepskyblue", 0xFF00BFFF); + sColorMap.put("dimgray", 0xFF696969); + sColorMap.put("dimgrey", 0xFF696969); + sColorMap.put("dodgerblue", 0xFF1E90FF); + sColorMap.put("firebrick", 0xFFB22222); + sColorMap.put("floralwhite", 0xFFFFFAF0); + sColorMap.put("forestgreen", 0xFF228B22); + sColorMap.put("fuchsia", 0xFFFF00FF); + sColorMap.put("gainsboro", 0xFFDCDCDC); + sColorMap.put("ghostwhite", 0xFFF8F8FF); + sColorMap.put("gold", 0xFFFFD700); + sColorMap.put("goldenrod", 0xFFDAA520); + sColorMap.put("gray", 0xFF808080); + sColorMap.put("grey", 0xFF808080); + sColorMap.put("green", 0xFF008000); + sColorMap.put("greenyellow", 0xFFADFF2F); + sColorMap.put("honeydew", 0xFFF0FFF0); + sColorMap.put("hotpink", 0xFFFF69B4); + sColorMap.put("indianred ", 0xFFCD5C5C); + sColorMap.put("indigo ", 0xFF4B0082); + sColorMap.put("ivory", 0xFFFFFFF0); + sColorMap.put("khaki", 0xFFF0E68C); + sColorMap.put("lavender", 0xFFE6E6FA); + sColorMap.put("lavenderblush", 0xFFFFF0F5); + sColorMap.put("lawngreen", 0xFF7CFC00); + sColorMap.put("lemonchiffon", 0xFFFFFACD); + sColorMap.put("lightblue", 0xFFADD8E6); + sColorMap.put("lightcoral", 0xFFF08080); + sColorMap.put("lightcyan", 0xFFE0FFFF); + sColorMap.put("lightgoldenrodyellow", 0xFFFAFAD2); + sColorMap.put("lightgray", 0xFFD3D3D3); + sColorMap.put("lightgrey", 0xFFD3D3D3); + sColorMap.put("lightgreen", 0xFF90EE90); + sColorMap.put("lightpink", 0xFFFFB6C1); + sColorMap.put("lightsalmon", 0xFFFFA07A); + sColorMap.put("lightseagreen", 0xFF20B2AA); + sColorMap.put("lightskyblue", 0xFF87CEFA); + sColorMap.put("lightslategray", 0xFF778899); + sColorMap.put("lightslategrey", 0xFF778899); + sColorMap.put("lightsteelblue", 0xFFB0C4DE); + sColorMap.put("lightyellow", 0xFFFFFFE0); + sColorMap.put("lime", 0xFF00FF00); + sColorMap.put("limegreen", 0xFF32CD32); + sColorMap.put("linen", 0xFFFAF0E6); + sColorMap.put("magenta", 0xFFFF00FF); + sColorMap.put("maroon", 0xFF800000); + sColorMap.put("mediumaquamarine", 0xFF66CDAA); + sColorMap.put("mediumblue", 0xFF0000CD); + sColorMap.put("mediumorchid", 0xFFBA55D3); + sColorMap.put("mediumpurple", 0xFF9370DB); + sColorMap.put("mediumseagreen", 0xFF3CB371); + sColorMap.put("mediumslateblue", 0xFF7B68EE); + sColorMap.put("mediumspringgreen", 0xFF00FA9A); + sColorMap.put("mediumturquoise", 0xFF48D1CC); + sColorMap.put("mediumvioletred", 0xFFC71585); + sColorMap.put("midnightblue", 0xFF191970); + sColorMap.put("mintcream", 0xFFF5FFFA); + sColorMap.put("mistyrose", 0xFFFFE4E1); + sColorMap.put("moccasin", 0xFFFFE4B5); + sColorMap.put("navajowhite", 0xFFFFDEAD); + sColorMap.put("navy", 0xFF000080); + sColorMap.put("oldlace", 0xFFFDF5E6); + sColorMap.put("olive", 0xFF808000); + sColorMap.put("olivedrab", 0xFF6B8E23); + sColorMap.put("orange", 0xFFFFA500); + sColorMap.put("orangered", 0xFFFF4500); + sColorMap.put("orchid", 0xFFDA70D6); + sColorMap.put("palegoldenrod", 0xFFEEE8AA); + sColorMap.put("palegreen", 0xFF98FB98); + sColorMap.put("paleturquoise", 0xFFAFEEEE); + sColorMap.put("palevioletred", 0xFFDB7093); + sColorMap.put("papayawhip", 0xFFFFEFD5); + sColorMap.put("peachpuff", 0xFFFFDAB9); + sColorMap.put("peru", 0xFFCD853F); + sColorMap.put("pink", 0xFFFFC0CB); + sColorMap.put("plum", 0xFFDDA0DD); + sColorMap.put("powderblue", 0xFFB0E0E6); + sColorMap.put("purple", 0xFF800080); + sColorMap.put("rebeccapurple", 0xFF663399); + sColorMap.put("red", 0xFFFF0000); + sColorMap.put("rosybrown", 0xFFBC8F8F); + sColorMap.put("royalblue", 0xFF4169E1); + sColorMap.put("saddlebrown", 0xFF8B4513); + sColorMap.put("salmon", 0xFFFA8072); + sColorMap.put("sandybrown", 0xFFF4A460); + sColorMap.put("seagreen", 0xFF2E8B57); + sColorMap.put("seashell", 0xFFFFF5EE); + sColorMap.put("sienna", 0xFFA0522D); + sColorMap.put("silver", 0xFFC0C0C0); + sColorMap.put("skyblue", 0xFF87CEEB); + sColorMap.put("slateblue", 0xFF6A5ACD); + sColorMap.put("slategray", 0xFF708090); + sColorMap.put("slategrey", 0xFF708090); + sColorMap.put("snow", 0xFFFFFAFA); + sColorMap.put("springgreen", 0xFF00FF7F); + sColorMap.put("steelblue", 0xFF4682B4); + sColorMap.put("tan", 0xFFD2B48C); + sColorMap.put("teal", 0xFF008080); + sColorMap.put("thistle", 0xFFD8BFD8); + sColorMap.put("tomato", 0xFFFF6347); + sColorMap.put("turquoise", 0xFF40E0D0); + sColorMap.put("violet", 0xFFEE82EE); + sColorMap.put("wheat", 0xFFF5DEB3); + sColorMap.put("white", 0xFFFFFFFF); + sColorMap.put("whitesmoke", 0xFFF5F5F5); + sColorMap.put("yellow", 0xFFFFFF00); + sColorMap.put("yellowgreen", 0xFF9ACD32); + } + + private static Pattern getTextAlignPattern() { + if (sTextAlignPattern == null) { + sTextAlignPattern = Pattern.compile("(?:\\s+|\\A)text-align\\s*:\\s*(\\S*)\\b"); + } + return sTextAlignPattern; + } + + private static Pattern getForegroundColorPattern() { + if (sForegroundColorPattern == null) { + sForegroundColorPattern = Pattern.compile( + "(?:\\s+|\\A)color\\s*:\\s*(\\S*)\\b"); + } + return sForegroundColorPattern; + } + + private static Pattern getBackgroundColorPattern() { + if (sBackgroundColorPattern == null) { + sBackgroundColorPattern = Pattern.compile( + "(?:\\s+|\\A)background(?:-color)?\\s*:\\s*(\\S*)\\b"); + } + return sBackgroundColorPattern; + } + + private static Pattern getTextDecorationPattern() { + if (sTextDecorationPattern == null) { + sTextDecorationPattern = Pattern.compile( + "(?:\\s+|\\A)text-decoration\\s*:\\s*(\\S*)\\b"); + } + return sTextDecorationPattern; + } + + private static Pattern getFontSizePattern() { + if (sTextFontSizePattern == null) { + sTextFontSizePattern = Pattern.compile( + "(?:\\s+|\\A)font-size\\s*:\\s*(\\S*)\\b"); + } + return sTextFontSizePattern; + } + + HtmlToSpannedConverter(Context context, String source, HtmlCompat.ImageGetter imageGetter, + HtmlCompat.TagHandler tagHandler, HtmlCompat.SpanCallback spanCallback, + Parser parser, int flags) { + mContext = context; + mSource = source; + mSpannableStringBuilder = new SpannableStringBuilder(); + mImageGetter = imageGetter; + mTagHandler = tagHandler; + mSpanCallback = spanCallback; + mReader = parser; + mFlags = flags; + } + + Spanned convert() { + mReader.setContentHandler(this); + try { + mReader.parse(new InputSource(new StringReader(mSource))); + } catch (IOException e) { + // We are reading from a string. There should not be IO problems. + throw new RuntimeException(e); + } catch (SAXException e) { + // TagSoup doesn't throw parse exceptions. + throw new RuntimeException(e); + } + // Fix flags and range for paragraph-type markup. + Object[] spans = mSpannableStringBuilder.getSpans(0, mSpannableStringBuilder.length(), ParagraphStyle.class); + for (Object span : spans) { + int start = mSpannableStringBuilder.getSpanStart(span); + int end = mSpannableStringBuilder.getSpanEnd(span); + // If the last line of the range is blank, back off by one. + if (end - 2 >= 0) { + if (mSpannableStringBuilder.charAt(end - 1) == '\n' && + mSpannableStringBuilder.charAt(end - 2) == '\n') { + end--; + } + } + if (end == start) { + mSpannableStringBuilder.removeSpan(span); + } else { + mSpannableStringBuilder.setSpan(span, start, end, Spannable.SPAN_PARAGRAPH); + } + } + return mSpannableStringBuilder; + } + + private void handleStartTag(String tag, Attributes attributes) { + if (tag.equalsIgnoreCase("br")) { + // We don't need to handle this. TagSoup will ensure that there's a
for each
+ // so we can safely emit the linebreaks when we handle the close tag. + } else if (tag.equalsIgnoreCase("p")) { + startBlockElement(mSpannableStringBuilder, attributes, getMarginParagraph()); + startCssStyle(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("ul")) { + startBlockElement(mSpannableStringBuilder, attributes, getMarginList()); + } else if (tag.equalsIgnoreCase("li")) { + startLi(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("div")) { + startBlockElement(mSpannableStringBuilder, attributes, getMarginDiv()); + } else if (tag.equalsIgnoreCase("span")) { + startCssStyle(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("strong")) { + start(mSpannableStringBuilder, new Bold()); + } else if (tag.equalsIgnoreCase("b")) { + start(mSpannableStringBuilder, new Bold()); + } else if (tag.equalsIgnoreCase("em")) { + start(mSpannableStringBuilder, new Italic()); + } else if (tag.equalsIgnoreCase("cite")) { + start(mSpannableStringBuilder, new Italic()); + } else if (tag.equalsIgnoreCase("dfn")) { + start(mSpannableStringBuilder, new Italic()); + } else if (tag.equalsIgnoreCase("i")) { + start(mSpannableStringBuilder, new Italic()); + } else if (tag.equalsIgnoreCase("big")) { + start(mSpannableStringBuilder, new Big()); + } else if (tag.equalsIgnoreCase("small")) { + start(mSpannableStringBuilder, new Small()); + } else if (tag.equalsIgnoreCase("font")) { + startFont(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("blockquote")) { + startBlockquote(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("tt")) { + start(mSpannableStringBuilder, new Monospace()); + } else if (tag.equalsIgnoreCase("a")) { + startA(mSpannableStringBuilder, attributes); + } else if (tag.equalsIgnoreCase("u")) { + start(mSpannableStringBuilder, new Underline()); + } else if (tag.equalsIgnoreCase("del")) { + start(mSpannableStringBuilder, new Strikethrough()); + } else if (tag.equalsIgnoreCase("s")) { + start(mSpannableStringBuilder, new Strikethrough()); + } else if (tag.equalsIgnoreCase("strike")) { + start(mSpannableStringBuilder, new Strikethrough()); + } else if (tag.equalsIgnoreCase("sup")) { + start(mSpannableStringBuilder, new Super()); + } else if (tag.equalsIgnoreCase("sub")) { + start(mSpannableStringBuilder, new Sub()); + } else if (tag.length() == 2 && + Character.toLowerCase(tag.charAt(0)) == 'h' && + tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { + startHeading(mSpannableStringBuilder, attributes, tag.charAt(1) - '1'); + } else if (tag.equalsIgnoreCase("img")) { + startImg(mSpannableStringBuilder, attributes, mImageGetter); + } else if (mTagHandler != null) { + mTagHandler.handleTag(true, tag, attributes, mSpannableStringBuilder, mReader); + } + } + + private void handleEndTag(String tag) { + if (tag.equalsIgnoreCase("br")) { + handleBr(mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("p")) { + endCssStyle(tag, mSpannableStringBuilder); + endBlockElement(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("ul")) { + endBlockElement(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("li")) { + endLi(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("div")) { + endBlockElement(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("span")) { + endCssStyle(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("strong")) { + end(tag, mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); + } else if (tag.equalsIgnoreCase("b")) { + end(tag, mSpannableStringBuilder, Bold.class, new StyleSpan(Typeface.BOLD)); + } else if (tag.equalsIgnoreCase("em")) { + end(tag, mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); + } else if (tag.equalsIgnoreCase("cite")) { + end(tag, mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); + } else if (tag.equalsIgnoreCase("dfn")) { + end(tag, mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); + } else if (tag.equalsIgnoreCase("i")) { + end(tag, mSpannableStringBuilder, Italic.class, new StyleSpan(Typeface.ITALIC)); + } else if (tag.equalsIgnoreCase("big")) { + end(tag, mSpannableStringBuilder, Big.class, new RelativeSizeSpan(1.25f)); + } else if (tag.equalsIgnoreCase("small")) { + end(tag, mSpannableStringBuilder, Small.class, new RelativeSizeSpan(0.8f)); + } else if (tag.equalsIgnoreCase("font")) { + endFont(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("blockquote")) { + endBlockquote(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("tt")) { + end(tag, mSpannableStringBuilder, Monospace.class, new TypefaceSpan("monospace")); + } else if (tag.equalsIgnoreCase("a")) { + endA(tag, mSpannableStringBuilder); + } else if (tag.equalsIgnoreCase("u")) { + end(tag, mSpannableStringBuilder, Underline.class, new UnderlineSpan()); + } else if (tag.equalsIgnoreCase("del")) { + end(tag, mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); + } else if (tag.equalsIgnoreCase("s")) { + end(tag, mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); + } else if (tag.equalsIgnoreCase("strike")) { + end(tag, mSpannableStringBuilder, Strikethrough.class, new StrikethroughSpan()); + } else if (tag.equalsIgnoreCase("sup")) { + end(tag, mSpannableStringBuilder, Super.class, new SuperscriptSpan()); + } else if (tag.equalsIgnoreCase("sub")) { + end(tag, mSpannableStringBuilder, Sub.class, new SubscriptSpan()); + } else if (tag.length() == 2 && + Character.toLowerCase(tag.charAt(0)) == 'h' && + tag.charAt(1) >= '1' && tag.charAt(1) <= '6') { + endHeading(tag, mSpannableStringBuilder); + } else if (mTagHandler != null) { + mTagHandler.handleTag(false, tag, null, mSpannableStringBuilder, mReader); + } + } + + private int getMarginParagraph() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_PARAGRAPH); + } + + private int getMarginHeading() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_HEADING); + } + + private int getMarginListItem() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM); + } + + private int getMarginList() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST); + } + + private int getMarginDiv() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_DIV); + } + + private int getMarginBlockquote() { + return getMargin(HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_BLOCKQUOTE); + } + + /** + * Returns the minimum number of newline characters needed before and after a given block-level + * element. + * + * @param flag the corresponding option flag defined in {@link HtmlCompat} of a block-level element + */ + private int getMargin(int flag) { + if ((flag & mFlags) != 0) { + return 1; + } + return 2; + } + + private void appendNewlines(Editable text, int minNewline) { + final int len = text.length(); + if (len == 0) { + return; + } + int existingNewlines = 0; + for (int i = len - 1; i >= 0 && text.charAt(i) == '\n'; i--) { + existingNewlines++; + } + for (int j = existingNewlines; j < minNewline; j++) { + text.append("\n"); + } + } + + private void startBlockElement(Editable text, Attributes attributes, int margin) { + if (margin > 0) { + appendNewlines(text, margin); + start(text, new Newline(margin)); + } + String style = attributes.getValue("", "style"); + if (style != null) { + Matcher m = getTextAlignPattern().matcher(style); + if (m.find()) { + String alignment = m.group(1); + if (alignment.equalsIgnoreCase("start")) { + start(text, new Alignment(Layout.Alignment.ALIGN_NORMAL)); + } else if (alignment.equalsIgnoreCase("center")) { + start(text, new Alignment(Layout.Alignment.ALIGN_CENTER)); + } else if (alignment.equalsIgnoreCase("end")) { + start(text, new Alignment(Layout.Alignment.ALIGN_OPPOSITE)); + } + } + } + } + + private void endBlockElement(String tag, Editable text) { + Newline n = getLast(text, Newline.class); + if (n != null) { + appendNewlines(text, n.mNumNewlines); + text.removeSpan(n); + } + Alignment a = getLast(text, Alignment.class); + if (a != null) { + setSpanFromMark(tag, text, a, new AlignmentSpan.Standard(a.mAlignment)); + } + } + + private void handleBr(Editable text) { + text.append('\n'); + } + + private void startLi(Editable text, Attributes attributes) { + startBlockElement(text, attributes, getMarginListItem()); + start(text, new Bullet()); + startCssStyle(text, attributes); + } + + private void endLi(String tag, Editable text) { + endCssStyle(tag, text); + endBlockElement(tag, text); + end(tag, text, Bullet.class, new BulletSpan()); + } + + private void startBlockquote(Editable text, Attributes attributes) { + startBlockElement(text, attributes, getMarginBlockquote()); + start(text, new Blockquote()); + } + + private void endBlockquote(String tag, Editable text) { + endBlockElement(tag, text); + end(tag, text, Blockquote.class, new QuoteSpan()); + } + + private void startHeading(Editable text, Attributes attributes, int level) { + startBlockElement(text, attributes, getMarginHeading()); + start(text, new Heading(level)); + } + + private void endHeading(String tag, Editable text) { + // RelativeSizeSpan and StyleSpan are CharacterStyles + // Their ranges should not include the newlines at the end + Heading h = getLast(text, Heading.class); + if (h != null) { + setSpanFromMark(tag, text, h, new RelativeSizeSpan(HEADING_SIZES[h.mLevel]), + new StyleSpan(Typeface.BOLD)); + } + endBlockElement(tag, text); + } + + private T getLast(Spanned text, Class kind) { + /* + * This knows that the last returned object from getSpans() + * will be the most recently added. + */ + T[] objs = text.getSpans(0, text.length(), kind); + if (objs.length == 0) { + return null; + } else { + return objs[objs.length - 1]; + } + } + + private void setSpanFromMark(String tag, Spannable text, Object mark, Object... spans) { + int where = text.getSpanStart(mark); + text.removeSpan(mark); + int len = text.length(); + if (where != len) { + for (Object span : spans) { + if (mSpanCallback != null) { + span = mSpanCallback.onSpanCreated(tag, span); + } + text.setSpan(span, where, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + private void start(Editable text, Object mark) { + int len = text.length(); + text.setSpan(mark, len, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + } + + private void end(String tag, Editable text, Class kind, Object repl) { + Object obj = getLast(text, kind); + if (obj != null) { + setSpanFromMark(tag, text, obj, repl); + } + } + + private void startCssStyle(Editable text, Attributes attributes) { + String style = attributes.getValue("", "style"); + if (style != null) { + Matcher m = getForegroundColorPattern().matcher(style); + if (m.find()) { + int c = getHtmlColor(m.group(1)); + if (c != -1) { + start(text, new Foreground(c | 0xFF000000)); + } + } + m = getBackgroundColorPattern().matcher(style); + if (m.find()) { + int c = getHtmlColor(m.group(1)); + if (c != -1) { + start(text, new Background(c | 0xFF000000)); + } + } + m = getTextDecorationPattern().matcher(style); + if (m.find()) { + String textDecoration = m.group(1); + if (textDecoration.equalsIgnoreCase("line-through")) { + start(text, new Strikethrough()); + } + } + m = getFontSizePattern().matcher(style); + if (m.find()) { + String textSizeString = m.group(1); + if (!TextUtils.isEmpty(textSizeString)) { + if (textSizeString.contains("px")) { + int textSize = Integer.valueOf(textSizeString.replaceAll("\\D+", "")); + textSize *= mContext.getResources().getDisplayMetrics().density; + start(text, new AbsoluteSize(textSize)); + } + if (textSizeString.contains("em")) { + float textSize = Float.valueOf(textSizeString.replaceAll("\\D+", "")); + start(text, new RelativeSize(textSize)); + } + } + } + } + } + + private void endCssStyle(String tag, Editable text) { + Strikethrough s = getLast(text, Strikethrough.class); + if (s != null) { + setSpanFromMark(tag, text, s, new StrikethroughSpan()); + } + Background b = getLast(text, Background.class); + if (b != null) { + setSpanFromMark(tag, text, b, new BackgroundColorSpan(b.mBackgroundColor)); + } + Foreground f = getLast(text, Foreground.class); + if (f != null) { + setSpanFromMark(tag, text, f, new ForegroundColorSpan(f.mForegroundColor)); + } + AbsoluteSize a = getLast(text, AbsoluteSize.class); + if (a != null) { + setSpanFromMark(tag, text, a, new AbsoluteSizeSpan(a.getTextSize())); + } + RelativeSize r = getLast(text, RelativeSize.class); + if (r != null) { + setSpanFromMark(tag, text, r, new RelativeSizeSpan(r.getTextProportion())); + } + } + + private void startImg(Editable text, Attributes attributes, HtmlCompat.ImageGetter img) { + String src = attributes.getValue("", "src"); + Drawable d = null; + if (img != null) { + d = img.getDrawable(src, attributes); + } + if (d == null) { + Resources res = mContext.getResources(); + d = res.getDrawable(R.drawable.weex_error); + d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + } + int len = text.length(); + text.append("\uFFFC"); + text.setSpan(new ImageSpan(d, src), len, text.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + + private void startFont(Editable text, Attributes attributes) { + String color = attributes.getValue("", "color"); + String face = attributes.getValue("", "face"); + if (!TextUtils.isEmpty(color)) { + int c = getHtmlColor(color); + if (c != -1) { + start(text, new Foreground(c | 0xFF000000)); + } + } + if (!TextUtils.isEmpty(face)) { + start(text, new Font(face)); + } + } + + private void endFont(String tag, Editable text) { + Font font = getLast(text, Font.class); + if (font != null) { + setSpanFromMark(tag, text, font, new TypefaceSpan(font.mFace)); + } + Foreground foreground = getLast(text, Foreground.class); + if (foreground != null) { + setSpanFromMark(tag, text, foreground, + new ForegroundColorSpan(foreground.mForegroundColor)); + } + } + + private void startA(Editable text, Attributes attributes) { + String href = attributes.getValue("", "href"); + start(text, new Href(href)); + } + + private void endA(String tag, Editable text) { + Href h = getLast(text, Href.class); + if (h != null) { + if (h.mHref != null) { + setSpanFromMark(tag, text, h, new HtmlCompat.DefensiveURLSpan((h.mHref))); + } + } + } + + private int getHtmlColor(String color) { + if ((mFlags & HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS) + == HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS) { + Integer i = sColorMap.get(color.toLowerCase(Locale.US)); + if (i != null) { + return i; + } + } + return ColorUtils.getHtmlColor(color); + } + + public void setDocumentLocator(Locator locator) { + } + + public void startDocument() throws SAXException { + } + + public void endDocument() throws SAXException { + } + + public void startPrefixMapping(String prefix, String uri) throws SAXException { + } + + public void endPrefixMapping(String prefix) throws SAXException { + } + + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + handleStartTag(localName, attributes); + } + + public void endElement(String uri, String localName, String qName) throws SAXException { + handleEndTag(localName); + } + + public void characters(char ch[], int start, int length) throws SAXException { + StringBuilder sb = new StringBuilder(); + /* + * Ignore whitespace that immediately follows other whitespace; + * newlines count as spaces. + */ + for (int i = 0; i < length; i++) { + char c = ch[i + start]; + if (c == ' ' || c == '\n') { + char pred; + int len = sb.length(); + if (len == 0) { + len = mSpannableStringBuilder.length(); + if (len == 0) { + pred = '\n'; + } else { + pred = mSpannableStringBuilder.charAt(len - 1); + } + } else { + pred = sb.charAt(len - 1); + } + if (pred != ' ' && pred != '\n') { + sb.append(' '); + } + } else { + sb.append(c); + } + } + mSpannableStringBuilder.append(sb); + } + + public void ignorableWhitespace(char ch[], int start, int length) throws SAXException { + } + + public void processingInstruction(String target, String data) throws SAXException { + } + + public void skippedEntity(String name) throws SAXException { + } + + private static class Bold { + } + + private static class Italic { + } + + private static class Underline { + } + + private static class Strikethrough { + } + + private static class Big { + } + + private static class Small { + } + + private static class Monospace { + } + + private static class Blockquote { + } + + private static class Super { + } + + private static class Sub { + } + + private static class Bullet { + } + + private static class Font { + String mFace; + + Font(String face) { + mFace = face; + } + } + + private static class Href { + String mHref; + + Href(String href) { + mHref = href; + } + } + + private static class Foreground { + private int mForegroundColor; + + Foreground(int foregroundColor) { + mForegroundColor = foregroundColor; + } + } + + private static class Background { + private int mBackgroundColor; + + Background(int backgroundColor) { + mBackgroundColor = backgroundColor; + } + } + + private static class Heading { + private int mLevel; + + Heading(int level) { + mLevel = level; + } + } + + private static class AbsoluteSize { + private int mTextSize; + + AbsoluteSize(int textSize) { + mTextSize = textSize; + } + + public int getTextSize() { + return mTextSize; + } + } + + private static class RelativeSize { + private float mTextProportion; + + RelativeSize(float textProportion) { + mTextProportion = textProportion; + } + + public float getTextProportion() { + return mTextProportion; + } + } + + private static class Newline { + private int mNumNewlines; + + Newline(int numNewlines) { + mNumNewlines = numNewlines; + } + } + + private static class Alignment { + private Layout.Alignment mAlignment; + + Alignment(Layout.Alignment alignment) { + mAlignment = alignment; + } + } + +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/XmlUtils.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/XmlUtils.java new file mode 100755 index 0000000000..8d5d8991ef --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/XmlUtils.java @@ -0,0 +1,833 @@ +/** + * 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 com.taobao.weex.ui.component.html.htmlcompat; + +import android.util.Xml; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class XmlUtils +{ + public static void skipCurrentTag(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG + || parser.getDepth() > outerDepth)) { + } + } + public static final int + convertValueToList(CharSequence value, String[] options, int defaultValue) + { + if (null != value) { + for (int i = 0; i < options.length; i++) { + if (value.equals(options[i])) + return i; + } + } + return defaultValue; + } + public static final boolean + convertValueToBoolean(CharSequence value, boolean defaultValue) + { + boolean result = false; + if (null == value) + return defaultValue; + if (value.equals("1") + || value.equals("true") + || value.equals("TRUE")) + result = true; + return result; + } + public static final int + convertValueToInt(CharSequence charSeq, int defaultValue) + { + if (null == charSeq) + return defaultValue; + String nm = charSeq.toString(); + // XXX This code is copied from Integer.decode() so we don't + // have to instantiate an Integer! + int value; + int sign = 1; + int index = 0; + int len = nm.length(); + int base = 10; + if ('-' == nm.charAt(0)) { + sign = -1; + index++; + } + if ('0' == nm.charAt(index)) { + // Quick check for a zero by itself + if (index == (len - 1)) + return 0; + char c = nm.charAt(index + 1); + if ('x' == c || 'X' == c) { + index += 2; + base = 16; + } else { + index++; + base = 8; + } + } + else if ('#' == nm.charAt(index)) + { + index++; + base = 16; + } + return Integer.parseInt(nm.substring(index), base) * sign; + } + public static final int + convertValueToUnsignedInt(String value, int defaultValue) + { + if (null == value) + return defaultValue; + return parseUnsignedIntAttribute(value); + } + public static final int + parseUnsignedIntAttribute(CharSequence charSeq) + { + String value = charSeq.toString(); + long bits; + int index = 0; + int len = value.length(); + int base = 10; + if ('0' == value.charAt(index)) { + // Quick check for zero by itself + if (index == (len - 1)) + return 0; + char c = value.charAt(index + 1); + if ('x' == c || 'X' == c) { // check for hex + index += 2; + base = 16; + } else { // check for octal + index++; + base = 8; + } + } else if ('#' == value.charAt(index)) { + index++; + base = 16; + } + return (int) Long.parseLong(value.substring(index), base); + } + /** + * Flatten a Map into an output stream as XML. The map can later be + * read back with readMapXml(). + * + * @param val The map to be flattened. + * @param out Where to write the XML data. + * + * @see #writeMapXml(Map, String, XmlSerializer) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, OutputStream out) + throws XmlPullParserException, IOException { + // FIXME not supported + throw new IllegalStateException("Not implemented"); +// XmlSerializer serializer = new FastXmlSerializer(); +// serializer.setOutput(out, "utf-8"); +// serializer.startDocument(null, true); +// serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); +// writeMapXml(val, null, serializer); +// serializer.endDocument(); + } + /** + * Flatten a List into an output stream as XML. The list can later be + * read back with readListXml(). + * + * @param val The list to be flattened. + * @param out Where to write the XML data. + * + * @see #writeListXml(List, String, XmlSerializer) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, OutputStream out) + throws XmlPullParserException, IOException + { + XmlSerializer serializer = Xml.newSerializer(); + serializer.setOutput(out, "utf-8"); + serializer.startDocument(null, true); + serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); + writeListXml(val, null, serializer); + serializer.endDocument(); + } + /** + * Flatten a Map into an XmlSerializer. The map can later be read back + * with readThisMapXml(). + * + * @param val The map to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the map into. + * + * @see #writeMapXml(Map, OutputStream) + * @see #writeListXml + * @see #writeValueXml + * @see #readMapXml + */ + public static final void writeMapXml(Map val, String name, XmlSerializer out) + throws XmlPullParserException, IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + Set s = val.entrySet(); + Iterator i = s.iterator(); + out.startTag(null, "map"); + if (name != null) { + out.attribute(null, "name", name); + } + while (i.hasNext()) { + Map.Entry e = (Map.Entry)i.next(); + writeValueXml(e.getValue(), (String)e.getKey(), out); + } + out.endTag(null, "map"); + } + /** + * Flatten a List into an XmlSerializer. The list can later be read back + * with readThisListXml(). + * + * @param val The list to be flattened. + * @param name Name attribute to include with this list's tag, or null for + * none. + * @param out XmlSerializer to write the list into. + * + * @see #writeListXml(List, OutputStream) + * @see #writeMapXml + * @see #writeValueXml + * @see #readListXml + */ + public static final void writeListXml(List val, String name, XmlSerializer out) + throws XmlPullParserException, IOException + { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "list"); + if (name != null) { + out.attribute(null, "name", name); + } + int N = val.size(); + int i=0; + while (i < N) { + writeValueXml(val.get(i), null, out); + i++; + } + out.endTag(null, "list"); + } + + public static final void writeSetXml(Set val, String name, XmlSerializer out) + throws XmlPullParserException, IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + + out.startTag(null, "set"); + if (name != null) { + out.attribute(null, "name", name); + } + + for (Object v : val) { + writeValueXml(v, null, out); + } + + out.endTag(null, "set"); + } + /** + * Flatten a byte[] into an XmlSerializer. The list can later be read back + * with readThisByteArrayXml(). + * + * @param val The byte array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + */ + public static final void writeByteArrayXml(byte[] val, String name, + XmlSerializer out) + throws XmlPullParserException, IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "byte-array"); + if (name != null) { + out.attribute(null, "name", name); + } + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + StringBuilder sb = new StringBuilder(val.length*2); + for (int i=0; i>4; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + h = b&0xff; + sb.append(h >= 10 ? ('a'+h-10) : ('0'+h)); + } + out.text(sb.toString()); + out.endTag(null, "byte-array"); + } + /** + * Flatten an int[] into an XmlSerializer. The list can later be read back + * with readThisIntArrayXml(). + * + * @param val The int array to be flattened. + * @param name Name attribute to include with this array's tag, or null for + * none. + * @param out XmlSerializer to write the array into. + * + * @see #writeMapXml + * @see #writeValueXml + * @see #readThisIntArrayXml + */ + public static final void writeIntArrayXml(int[] val, String name, + XmlSerializer out) + throws XmlPullParserException, IOException { + if (val == null) { + out.startTag(null, "null"); + out.endTag(null, "null"); + return; + } + out.startTag(null, "int-array"); + if (name != null) { + out.attribute(null, "name", name); + } + final int N = val.length; + out.attribute(null, "num", Integer.toString(N)); + for (int i=0; iafter the tag that begins the map. + * + * @param parser The XmlPullParser from which to read the map data. + * @param endTag Name of the tag that will end the map, usually "map". + * @param name An array of one string, used to return the name attribute + * of the map's tag. + * + * @return HashMap The newly generated map. + * + * @see #readMapXml + */ + public static final HashMap readThisMapXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, IOException + { + HashMap map = new HashMap(); + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + if (name[0] != null) { + //System.out.println("Adding to map: " + name + " -> " + val); + map.put(name[0], val); + } else { + throw new XmlPullParserException( + "Map value without name attribute: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return map; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read an ArrayList object from an XmlPullParser. The XML data could + * previously have been generated by writeListXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return HashMap The newly generated list. + * + * @see #readListXml + */ + public static final ArrayList readThisListXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, IOException + { + ArrayList list = new ArrayList(); + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + list.add(val); + //System.out.println("Adding to list: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return list; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + + /** + * Read a HashSet object from an XmlPullParser. The XML data could previously + * have been generated by writeSetXml(). The XmlPullParser must be positioned + * after the tag that begins the set. + * + * @param parser The XmlPullParser from which to read the set data. + * @param endTag Name of the tag that will end the set, usually "set". + * @param name An array of one string, used to return the name attribute + * of the set's tag. + * + * @return HashSet The newly generated set. + * + * @throws XmlPullParserException + * @throws IOException + * + * @see #readSetXml + */ + public static final HashSet readThisSetXml(XmlPullParser parser, String endTag, String[] name) + throws XmlPullParserException, IOException { + HashSet set = new HashSet(); + + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + Object val = readThisValueXml(parser, name); + set.add(val); + //System.out.println("Adding to set: " + val); + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return set; + } + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + parser.getName()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read an int[] object from an XmlPullParser. The XML data could + * previously have been generated by writeIntArrayXml(). The XmlPullParser + * must be positioned after the tag that begins the list. + * + * @param parser The XmlPullParser from which to read the list data. + * @param endTag Name of the tag that will end the list, usually "list". + * @param name An array of one string, used to return the name attribute + * of the list's tag. + * + * @return Returns a newly generated int[]. + * + * @see #readListXml + */ + public static final int[] readThisIntArrayXml(XmlPullParser parser, + String endTag, String[] name) + throws XmlPullParserException, IOException { + int num; + try { + num = Integer.parseInt(parser.getAttributeValue(null, "num")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need num attribute in byte-array"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in num attribute in byte-array"); + } + int[] array = new int[num]; + int i = 0; + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + if (parser.getName().equals("item")) { + try { + array[i] = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } catch (NullPointerException e) { + throw new XmlPullParserException( + "Need value attribute in item"); + } catch (NumberFormatException e) { + throw new XmlPullParserException( + "Not a number in value attribute in item"); + } + } else { + throw new XmlPullParserException( + "Expected item tag at: " + parser.getName()); + } + } else if (eventType == parser.END_TAG) { + if (parser.getName().equals(endTag)) { + return array; + } else if (parser.getName().equals("item")) { + i++; + } else { + throw new XmlPullParserException( + "Expected " + endTag + " end tag at: " + + parser.getName()); + } + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Document ended before " + endTag + " end tag"); + } + /** + * Read a flattened object from an XmlPullParser. The XML data could + * previously have been written with writeMapXml(), writeListXml(), or + * writeValueXml(). The XmlPullParser must be positioned at the + * tag that defines the value. + * + * @param parser The XmlPullParser from which to read the object. + * @param name An array of one string, used to return the name attribute + * of the value's tag. + * + * @return Object The newly generated value object. + * + * @see #readMapXml + * @see #readListXml + * @see #writeValueXml + */ + public static final Object readValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, IOException + { + int eventType = parser.getEventType(); + do { + if (eventType == parser.START_TAG) { + return readThisValueXml(parser, name); + } else if (eventType == parser.END_TAG) { + throw new XmlPullParserException( + "Unexpected end tag at: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text: " + parser.getText()); + } + eventType = parser.next(); + } while (eventType != parser.END_DOCUMENT); + throw new XmlPullParserException( + "Unexpected end of document"); + } + private static final Object readThisValueXml(XmlPullParser parser, String[] name) + throws XmlPullParserException, IOException + { + final String valueName = parser.getAttributeValue(null, "name"); + final String tagName = parser.getName(); + //System.out.println("Reading this value tag: " + tagName + ", name=" + valueName); + Object res; + if (tagName.equals("null")) { + res = null; + } else if (tagName.equals("string")) { + String value = ""; + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals("string")) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + value); + return value; + } + throw new XmlPullParserException( + "Unexpected end tag in : " + parser.getName()); + } else if (eventType == parser.TEXT) { + value += parser.getText(); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in : " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in "); + } else if (tagName.equals("int")) { + res = Integer.parseInt(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("long")) { + res = Long.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("float")) { + res = new Float(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("double")) { + res = new Double(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("boolean")) { + res = Boolean.valueOf(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("int-array")) { + parser.next(); + res = readThisIntArrayXml(parser, "int-array", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("map")) { + parser.next(); + res = readThisMapXml(parser, "map", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("list")) { + parser.next(); + res = readThisListXml(parser, "list", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else if (tagName.equals("set")) { + parser.next(); + res = readThisSetXml(parser, "set", name); + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } else { + throw new XmlPullParserException( + "Unknown tag: " + tagName); + } + // Skip through to end tag. + int eventType; + while ((eventType = parser.next()) != parser.END_DOCUMENT) { + if (eventType == parser.END_TAG) { + if (parser.getName().equals(tagName)) { + name[0] = valueName; + //System.out.println("Returning value for " + valueName + ": " + res); + return res; + } + throw new XmlPullParserException( + "Unexpected end tag in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.TEXT) { + throw new XmlPullParserException( + "Unexpected text in <" + tagName + ">: " + parser.getName()); + } else if (eventType == parser.START_TAG) { + throw new XmlPullParserException( + "Unexpected start tag in <" + tagName + ">: " + parser.getName()); + } + } + throw new XmlPullParserException( + "Unexpected end of document in <" + tagName + ">"); + } + public static final void beginDocument(XmlPullParser parser, String firstElementName) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + if (type != parser.START_TAG) { + throw new XmlPullParserException("No start tag found"); + } + if (!parser.getName().equals(firstElementName)) { + throw new XmlPullParserException("Unexpected start tag: found " + parser.getName() + + ", expected " + firstElementName); + } + } + public static final void nextElement(XmlPullParser parser) throws XmlPullParserException, IOException + { + int type; + while ((type=parser.next()) != parser.START_TAG + && type != parser.END_DOCUMENT) { + ; + } + } + public static boolean nextElementWithin(XmlPullParser parser, int outerDepth) + throws IOException, XmlPullParserException { + for (;;) { + int type = parser.next(); + if (type == XmlPullParser.END_DOCUMENT + || (type == XmlPullParser.END_TAG && parser.getDepth() == outerDepth)) { + return false; + } + if (type == XmlPullParser.START_TAG + && parser.getDepth() == outerDepth + 1) { + return true; + } + } + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AttributesImpl.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AttributesImpl.java new file mode 100755 index 0000000000..a96ed6236d --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AttributesImpl.java @@ -0,0 +1,575 @@ +/** + * 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. + */ +// XMLWriter.java - serialize an XML document. +// Written by David Megginson, david@megginson.com +// and placed by him into the public domain. +// Extensively modified by John Cowan for TagSoup. +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +package org.ccil.cowan.tagsoup; +import org.xml.sax.Attributes; +/** + * Default implementation of the Attributes interface. + * + *
+ * This module, both source code and documentation, is in the + * Public Domain, and comes with NO WARRANTY. + * See http://www.saxproject.org + * for further information. + *
+ * + *

This class provides a default implementation of the SAX2 + * {@link Attributes Attributes} interface, with the + * addition of manipulators so that the list can be modified or + * reused.

+ * + *

There are two typical uses of this class:

+ * + *
    + *
  1. to take a persistent snapshot of an Attributes object + * in a {@link org.xml.sax.ContentHandler#startElement startElement} event; or
  2. + *
  3. to construct or modify an Attributes object in a SAX2 driver or filter.
  4. + *
+ * + *

This class replaces the now-deprecated SAX1 {@link + * org.xml.sax.helpers.AttributeListImpl AttributeListImpl} + * class; in addition to supporting the updated Attributes + * interface rather than the deprecated {@link org.xml.sax.AttributeList + * AttributeList} interface, it also includes a much more efficient + * implementation using a single array rather than a set of Vectors.

+ * + * @since SAX 2.0 + * @author David Megginson + * @version 2.0.1 (sax2r2) + */ +public class AttributesImpl implements Attributes +{ + + //////////////////////////////////////////////////////////////////// + // Constructors. + //////////////////////////////////////////////////////////////////// + /** + * Construct a new, empty AttributesImpl object. + */ + public AttributesImpl () + { + length = 0; + data = null; + } + /** + * Copy an existing Attributes object. + * + *

This constructor is especially useful inside a + * {@link org.xml.sax.ContentHandler#startElement startElement} event.

+ * + * @param atts The existing Attributes object. + */ + public AttributesImpl (Attributes atts) + { + setAttributes(atts); + } + + //////////////////////////////////////////////////////////////////// + // Implementation of org.xml.sax.Attributes. + //////////////////////////////////////////////////////////////////// + /** + * Return the number of attributes in the list. + * + * @return The number of attributes in the list. + * @see Attributes#getLength + */ + public int getLength () + { + return length; + } + /** + * Return an attribute's Namespace URI. + * + * @param index The attribute's index (zero-based). + * @return The Namespace URI, the empty string if none is + * available, or null if the index is out of range. + * @see Attributes#getURI + */ + public String getURI (int index) + { + if (index >= 0 && index < length) { + return data[index*5]; + } else { + return null; + } + } + /** + * Return an attribute's local name. + * + * @param index The attribute's index (zero-based). + * @return The attribute's local name, the empty string if + * none is available, or null if the index if out of range. + * @see Attributes#getLocalName + */ + public String getLocalName (int index) + { + if (index >= 0 && index < length) { + return data[index*5+1]; + } else { + return null; + } + } + /** + * Return an attribute's qualified (prefixed) name. + * + * @param index The attribute's index (zero-based). + * @return The attribute's qualified name, the empty string if + * none is available, or null if the index is out of bounds. + * @see Attributes#getQName + */ + public String getQName (int index) + { + if (index >= 0 && index < length) { + return data[index*5+2]; + } else { + return null; + } + } + /** + * Return an attribute's type by index. + * + * @param index The attribute's index (zero-based). + * @return The attribute's type, "CDATA" if the type is unknown, or null + * if the index is out of bounds. + * @see Attributes#getType(int) + */ + public String getType (int index) + { + if (index >= 0 && index < length) { + return data[index*5+3]; + } else { + return null; + } + } + /** + * Return an attribute's value by index. + * + * @param index The attribute's index (zero-based). + * @return The attribute's value or null if the index is out of bounds. + * @see Attributes#getValue(int) + */ + public String getValue (int index) + { + if (index >= 0 && index < length) { + return data[index*5+4]; + } else { + return null; + } + } + /** + * Look up an attribute's index by Namespace name. + * + *

In many cases, it will be more efficient to look up the name once and + * use the index query methods rather than using the name query methods + * repeatedly.

+ * + * @param uri The attribute's Namespace URI, or the empty + * string if none is available. + * @param localName The attribute's local name. + * @return The attribute's index, or -1 if none matches. + * @see Attributes#getIndex(String, String) + */ + public int getIndex (String uri, String localName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i].equals(uri) && data[i+1].equals(localName)) { + return i / 5; + } + } + return -1; + } + /** + * Look up an attribute's index by qualified (prefixed) name. + * + * @param qName The qualified name. + * @return The attribute's index, or -1 if none matches. + * @see Attributes#getIndex(String) + */ + public int getIndex (String qName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i+2].equals(qName)) { + return i / 5; + } + } + return -1; + } + /** + * Look up an attribute's type by Namespace-qualified name. + * + * @param uri The Namespace URI, or the empty string for a name + * with no explicit Namespace URI. + * @param localName The local name. + * @return The attribute's type, or null if there is no + * matching attribute. + * @see Attributes#getType(String, String) + */ + public String getType (String uri, String localName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i].equals(uri) && data[i+1].equals(localName)) { + return data[i+3]; + } + } + return null; + } + /** + * Look up an attribute's type by qualified (prefixed) name. + * + * @param qName The qualified name. + * @return The attribute's type, or null if there is no + * matching attribute. + * @see Attributes#getType(String) + */ + public String getType (String qName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i+2].equals(qName)) { + return data[i+3]; + } + } + return null; + } + /** + * Look up an attribute's value by Namespace-qualified name. + * + * @param uri The Namespace URI, or the empty string for a name + * with no explicit Namespace URI. + * @param localName The local name. + * @return The attribute's value, or null if there is no + * matching attribute. + * @see Attributes#getValue(String, String) + */ + public String getValue (String uri, String localName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i].equals(uri) && data[i+1].equals(localName)) { + return data[i+4]; + } + } + return null; + } + /** + * Look up an attribute's value by qualified (prefixed) name. + * + * @param qName The qualified name. + * @return The attribute's value, or null if there is no + * matching attribute. + * @see Attributes#getValue(String) + */ + public String getValue (String qName) + { + int max = length * 5; + for (int i = 0; i < max; i += 5) { + if (data[i+2].equals(qName)) { + return data[i+4]; + } + } + return null; + } + + //////////////////////////////////////////////////////////////////// + // Manipulators. + //////////////////////////////////////////////////////////////////// + /** + * Clear the attribute list for reuse. + * + *

Note that little memory is freed by this call: + * the current array is kept so it can be + * reused.

+ */ + public void clear () + { + if (data != null) { + for (int i = 0; i < (length * 5); i++) + data [i] = null; + } + length = 0; + } + /** + * Copy an entire Attributes object. + * + *

It may be more efficient to reuse an existing object + * rather than constantly allocating new ones.

+ * + * @param atts The attributes to copy. + */ + public void setAttributes (Attributes atts) + { + clear(); + length = atts.getLength(); + if (length > 0) { + data = new String[length*5]; + for (int i = 0; i < length; i++) { + data[i*5] = atts.getURI(i); + data[i*5+1] = atts.getLocalName(i); + data[i*5+2] = atts.getQName(i); + data[i*5+3] = atts.getType(i); + data[i*5+4] = atts.getValue(i); + } + } + } + /** + * Add an attribute to the end of the list. + * + *

For the sake of speed, this method does no checking + * to see if the attribute is already in the list: that is + * the responsibility of the application.

+ * + * @param uri The Namespace URI, or the empty string if + * none is available or Namespace processing is not + * being performed. + * @param localName The local name, or the empty string if + * Namespace processing is not being performed. + * @param qName The qualified (prefixed) name, or the empty string + * if qualified names are not available. + * @param type The attribute type as a string. + * @param value The attribute value. + */ + public void addAttribute (String uri, String localName, String qName, + String type, String value) + { + ensureCapacity(length+1); + data[length*5] = uri; + data[length*5+1] = localName; + data[length*5+2] = qName; + data[length*5+3] = type; + data[length*5+4] = value; + length++; + } + /** + * Set an attribute in the list. + * + *

For the sake of speed, this method does no checking + * for name conflicts or well-formedness: such checks are the + * responsibility of the application.

+ * + * @param index The index of the attribute (zero-based). + * @param uri The Namespace URI, or the empty string if + * none is available or Namespace processing is not + * being performed. + * @param localName The local name, or the empty string if + * Namespace processing is not being performed. + * @param qName The qualified name, or the empty string + * if qualified names are not available. + * @param type The attribute type as a string. + * @param value The attribute value. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setAttribute (int index, String uri, String localName, + String qName, String type, String value) + { + if (index >= 0 && index < length) { + data[index*5] = uri; + data[index*5+1] = localName; + data[index*5+2] = qName; + data[index*5+3] = type; + data[index*5+4] = value; + } else { + badIndex(index); + } + } + /** + * Remove an attribute from the list. + * + * @param index The index of the attribute (zero-based). + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void removeAttribute (int index) + { + if (index >= 0 && index < length) { + if (index < length - 1) { + System.arraycopy(data, (index+1)*5, data, index*5, + (length-index-1)*5); + } + index = (length - 1) * 5; + data [index++] = null; + data [index++] = null; + data [index++] = null; + data [index++] = null; + data [index] = null; + length--; + } else { + badIndex(index); + } + } + /** + * Set the Namespace URI of a specific attribute. + * + * @param index The index of the attribute (zero-based). + * @param uri The attribute's Namespace URI, or the empty + * string for none. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setURI (int index, String uri) + { + if (index >= 0 && index < length) { + data[index*5] = uri; + } else { + badIndex(index); + } + } + /** + * Set the local name of a specific attribute. + * + * @param index The index of the attribute (zero-based). + * @param localName The attribute's local name, or the empty + * string for none. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setLocalName (int index, String localName) + { + if (index >= 0 && index < length) { + data[index*5+1] = localName; + } else { + badIndex(index); + } + } + /** + * Set the qualified name of a specific attribute. + * + * @param index The index of the attribute (zero-based). + * @param qName The attribute's qualified name, or the empty + * string for none. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setQName (int index, String qName) + { + if (index >= 0 && index < length) { + data[index*5+2] = qName; + } else { + badIndex(index); + } + } + /** + * Set the type of a specific attribute. + * + * @param index The index of the attribute (zero-based). + * @param type The attribute's type. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setType (int index, String type) + { + if (index >= 0 && index < length) { + data[index*5+3] = type; + } else { + badIndex(index); + } + } + /** + * Set the value of a specific attribute. + * + * @param index The index of the attribute (zero-based). + * @param value The attribute's value. + * @exception ArrayIndexOutOfBoundsException When the + * supplied index does not point to an attribute + * in the list. + */ + public void setValue (int index, String value) + { + if (index >= 0 && index < length) { + data[index*5+4] = value; + } else { + badIndex(index); + } + } + + //////////////////////////////////////////////////////////////////// + // Internal methods. + //////////////////////////////////////////////////////////////////// + /** + * Ensure the internal array's capacity. + * + * @param n The minimum number of attributes that the array must + * be able to hold. + */ + private void ensureCapacity (int n) { + if (n <= 0) { + return; + } + int max; + if (data == null || data.length == 0) { + max = 25; + } + else if (data.length >= n * 5) { + return; + } + else { + max = data.length; + } + while (max < n * 5) { + max *= 2; + } + String newData[] = new String[max]; + if (length > 0) { + System.arraycopy(data, 0, newData, 0, length*5); + } + data = newData; + } + /** + * Report a bad array index in a manipulator. + * + * @param index The index to report. + * @exception ArrayIndexOutOfBoundsException Always. + */ + private void badIndex (int index) + throws ArrayIndexOutOfBoundsException + { + String msg = + "Attempt to modify attribute at illegal index: " + index; + throw new ArrayIndexOutOfBoundsException(msg); + } + + //////////////////////////////////////////////////////////////////// + // Internal state. + //////////////////////////////////////////////////////////////////// + int length; + String data []; +} +// end of AttributesImpl.java \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AutoDetector.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AutoDetector.java new file mode 100755 index 0000000000..e5b872fc2e --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/AutoDetector.java @@ -0,0 +1,55 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// Interface to objects that translate InputStreams to Readers by auto-detection +package org.ccil.cowan.tagsoup; +import java.io.Reader; +import java.io.InputStream; +/** + Classes which accept an InputStream and provide a Reader which figures + out the encoding of the InputStream and reads characters from it should + conform to this interface. + @see InputStream + @see Reader + */ +public interface AutoDetector { + /** + Given an InputStream, return a suitable Reader that understands + the presumed character encoding of that InputStream. + If bytes are consumed from the InputStream in the process, they + must be pushed back onto the InputStream so that they can be + reinterpreted as characters. + @param i The InputStream + @return A Reader that reads from the InputStream + */ + public Reader autoDetectingReader(InputStream i); +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Element.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Element.java new file mode 100755 index 0000000000..8c29817554 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Element.java @@ -0,0 +1,180 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +package org.ccil.cowan.tagsoup; +/** + The internal representation of an actual element (not an element type). + An Element has an element type, attributes, and a successor Element + for use in constructing stacks and queues of Elements. + @see ElementType + @see AttributesImpl + */ +public class Element { + private ElementType theType; // type of element + private AttributesImpl theAtts; // attributes of element + private Element theNext; // successor of element + private boolean preclosed; // this element has been preclosed + /** + Return an Element from a specified ElementType. + @param type The element type of the newly constructed element + @param defaultAttributes True if default attributes are wanted + */ + public Element(ElementType type, boolean defaultAttributes) { + theType = type; + if (defaultAttributes) theAtts = new AttributesImpl(type.atts()); + else theAtts = new AttributesImpl(); + theNext = null; + preclosed = false; + } + /** + Return the element type. + @return The element type. + */ + public ElementType type() { return theType; } + /** + Return the attributes as an AttributesImpl object. + Returning an AttributesImpl makes the attributes mutable. + @return The attributes + @see AttributesImpl + */ + public AttributesImpl atts() { return theAtts; } + /** + Return the next element in an element stack or queue. + @return The next element + */ + public Element next() { return theNext; } + /** + Change the next element in an element stack or queue. + @param next The new next element + */ + public void setNext(Element next) { theNext = next; } + /** + Return the name of the element's type. + Convenience method. + @return The element type name + */ + public String name() { return theType.name(); } + /** + Return the namespace name of the element's type. + Convenience method. + @return The element type namespace name + */ + public String namespace() { return theType.namespace(); } + /** + Return the local name of the element's type. + Convenience method. + @return The element type local name + */ + public String localName() { return theType.localName(); } + /** + Return the content model vector of the element's type. + Convenience method. + @return The content model vector + */ + public int model() { return theType.model(); } + /** + Return the member-of vector of the element's type. + Convenience method. + @return The member-of vector + */ + public int memberOf() { return theType.memberOf(); } + /** + Return the flags vector of the element's type. + Convenience method. + @return The flags vector + */ + public int flags() { return theType.flags(); } + /** + Return the parent element type of the element's type. + Convenience method. + @return The parent element type + */ + public ElementType parent() { return theType.parent(); } + /** + Return true if the type of this element can contain the type of + another element. + Convenience method. + @param other The other element + */ + public boolean canContain(Element other) { + return theType.canContain(other.theType); + } + /** + Set an attribute and its value into this element. + @param name The attribute name (Qname) + @param type The attribute type + @param value The attribute value + */ + public void setAttribute(String name, String type, String value) { + theType.setAttribute(theAtts, name, type, value); + } + /** + Make this element anonymous. + Remove any id or name attribute present + in the element's attributes. + */ + public void anonymize() { + for (int i = theAtts.getLength() - 1; i >= 0; i--) { + if (theAtts.getType(i).equals("ID") || + theAtts.getQName(i).equals("name")) { + theAtts.removeAttribute(i); + } + } + } + /** + Clean the attributes of this element. + Attributes with null name (the name was ill-formed) + or null value (the attribute was present in the element type but + not in this actual element) are removed. + */ + public void clean() { + for (int i = theAtts.getLength() - 1; i >= 0; i--) { + String name = theAtts.getLocalName(i); + if (theAtts.getValue(i) == null || name == null || + name.length() == 0) { + theAtts.removeAttribute(i); + continue; + } + } + } + /** + Force this element to preclosed status, meaning that an end-tag has + been seen but the element cannot yet be closed for structural reasons. + */ + public void preclose() { + preclosed = true; + } + /** + Return true if this element has been preclosed. + */ + public boolean isPreclosed() { + return preclosed; + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ElementType.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ElementType.java new file mode 100755 index 0000000000..00b547e407 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ElementType.java @@ -0,0 +1,250 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +package org.ccil.cowan.tagsoup; +/** + This class represents an element type in the schema. + An element type has a name, a content model vector, a member-of vector, + a flags vector, default attributes, and a schema to which it belongs. + @see Schema + */ +public class ElementType { + private String theName; // element type name (Qname) + private String theNamespace; // element type namespace name + private String theLocalName; // element type local name + private int theModel; // bitmap: what the element contains + private int theMemberOf; // bitmap: what element is contained in + private int theFlags; // bitmap: element flags + private AttributesImpl theAtts; // default attributes + private ElementType theParent; // parent of this element type + private Schema theSchema; // schema to which this belongs + /** + Construct an ElementType: + but it's better to use Schema.element() instead. + The content model, member-of, and flags vectors are specified as ints. + @param name The element type name + @param model ORed-together bits representing the content models + allowed in the content of this element type + @param memberOf ORed-together bits representing the content models + to which this element type belongs + @param flags ORed-together bits representing the flags associated + with this element type + @param schema The schema with which this element type will be + associated + */ + public ElementType(String name, int model, int memberOf, int flags, Schema schema) { + theName = name; + theModel = model; + theMemberOf = memberOf; + theFlags = flags; + theAtts = new AttributesImpl(); + theSchema = schema; + theNamespace = namespace(name, false); + theLocalName = localName(name); + } + /** + Return a namespace name from a Qname. + The attribute flag tells us whether to return an empty namespace + name if there is no prefix, or use the schema default instead. + @param name The Qname + @param attribute True if name is an attribute name + @return The namespace name + **/ + public String namespace(String name, boolean attribute) { + int colon = name.indexOf(':'); + if (colon == -1) { + return attribute ? "" : theSchema.getURI(); + } + String prefix = name.substring(0, colon); + if (prefix.equals("xml")) { + return "http://www.w3.org/XML/1998/namespace"; + } + else { + return ("urn:x-prefix:" + prefix).intern(); + } + } + /** + Return a local name from a Qname. + @param name The Qname + @return The local name + **/ + public String localName(String name) { + int colon = name.indexOf(':'); + if (colon == -1) { + return name; + } + else { + return name.substring(colon+1).intern(); + } + } + /** + Returns the name of this element type. + @return The name of the element type + */ + public String name() { return theName; } + /** + Returns the namespace name of this element type. + @return The namespace name of the element type + */ + public String namespace() { return theNamespace; } + /** + Returns the local name of this element type. + @return The local name of the element type + */ + public String localName() { return theLocalName; } + /** + Returns the content models of this element type. + @return The content models of this element type as a vector of bits + */ + public int model() { return theModel; } + /** + Returns the content models to which this element type belongs. + @return The content models to which this element type belongs as a + vector of bits + */ + public int memberOf() { return theMemberOf; } + /** + Returns the flags associated with this element type. + @return The flags associated with this element type as a vector of bits + */ + public int flags() { return theFlags; } + /** + Returns the default attributes associated with this element type. + Attributes of type CDATA that don't have default values are + typically not included. Other attributes without default values + have an internal value of null. + The return value is an AttributesImpl to allow the caller to mutate + the attributes. + */ + public AttributesImpl atts() {return theAtts;} + /** + Returns the parent element type of this element type. + @return The parent element type + */ + public ElementType parent() {return theParent;} + /** + Returns the schema which this element type is associated with. + @return The schema + */ + public Schema schema() {return theSchema;} + /** + Returns true if this element type can contain another element type. + That is, if any of the models in this element's model vector + match any of the models in the other element type's member-of + vector. + @param other The other element type + */ + public boolean canContain(ElementType other) { + return (theModel & other.theMemberOf) != 0; + } + /** + Sets an attribute and its value into an AttributesImpl object. + Attempts to set a namespace declaration are ignored. + @param atts The AttributesImpl object + @param name The name (Qname) of the attribute + @param type The type of the attribute + @param value The value of the attribute + */ + public void setAttribute(AttributesImpl atts, String name, String type, String value) { + if (name.equals("xmlns") || name.startsWith("xmlns:")) { + return; + } + ; + String namespace = namespace(name, true); + String localName = localName(name); + int i = atts.getIndex(name); + if (i == -1) { + name = name.intern(); + if (type == null) type = "CDATA"; + if (!type.equals("CDATA")) value = normalize(value); + atts.addAttribute(namespace, localName, name, type, value); + } + else { + if (type == null) type = atts.getType(i); + if (!type.equals("CDATA")) value=normalize(value); + atts.setAttribute(i, namespace, localName, name, type, value); + } + } + /** + Normalize an attribute value (ID-style). + CDATA-style attribute normalization is already done. + @param value The value to normalize + @return The normalized value + **/ + public static String normalize(String value) { + if (value == null) return value; + value = value.trim(); + if (value.indexOf(" ") == -1) return value; + boolean space = false; + int len = value.length(); + StringBuffer b = new StringBuffer(len); + for (int i = 0; i < len; i++) { + char v = value.charAt(i); + if (v == ' ') { + if (!space) b.append(v); + space = true; + } + else { + b.append(v); + space = false; + } + } + return b.toString(); + } + /** + Sets an attribute and its value into this element type. + @param name The name of the attribute + @param type The type of the attribute + @param value The value of the attribute + */ + public void setAttribute(String name, String type, String value) { + setAttribute(theAtts, name, type, value); + } + /** + Sets the models of this element type. + @param model The content models of this element type as a vector of bits + */ + public void setModel(int model) { theModel = model; } + /** + Sets the content models to which this element type belongs. + @param memberOf The content models to which this element type belongs as a vector of bits + */ + public void setMemberOf(int memberOf) { theMemberOf = memberOf; } + /** + Sets the flags of this element type. + @param flags associated with this element type The flags as a vector of bits + */ + public void setFlags(int flags) { theFlags = flags; } + /** + Sets the parent element type of this element type. + @param parent The parent element type + */ + public void setParent(ElementType parent) { theParent = parent; } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLModels.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLModels.java new file mode 100755 index 0000000000..d08b4ee04e --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLModels.java @@ -0,0 +1,65 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// Defines models for HTMLSchema +/** + This interface contains generated constants representing HTML content + models. Logically, it is part of HTMLSchema, but it is more + convenient to generate the constants into a separate interface. + */ +package org.ccil.cowan.tagsoup; +public interface HTMLModels { + // Start of model definitions + public static final int M_AREA = 1 << 1; + public static final int M_BLOCK = 1 << 2; + public static final int M_BLOCKINLINE = 1 << 3; + public static final int M_BODY = 1 << 4; + public static final int M_CELL = 1 << 5; + public static final int M_COL = 1 << 6; + public static final int M_DEF = 1 << 7; + public static final int M_FORM = 1 << 8; + public static final int M_FRAME = 1 << 9; + public static final int M_HEAD = 1 << 10; + public static final int M_HTML = 1 << 11; + public static final int M_INLINE = 1 << 12; + public static final int M_LEGEND = 1 << 13; + public static final int M_LI = 1 << 14; + public static final int M_NOLINK = 1 << 15; + public static final int M_OPTION = 1 << 16; + public static final int M_OPTIONS = 1 << 17; + public static final int M_P = 1 << 18; + public static final int M_PARAM = 1 << 19; + public static final int M_TABLE = 1 << 20; + public static final int M_TABULAR = 1 << 21; + public static final int M_TR = 1 << 22; + // End of model definitions +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLScanner.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLScanner.java new file mode 100755 index 0000000000..9d423c6c37 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLScanner.java @@ -0,0 +1,687 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +package org.ccil.cowan.tagsoup; +import java.io.*; +import org.xml.sax.SAXException; +import org.xml.sax.Locator; +/** + This class implements a table-driven scanner for HTML, allowing for lots of + defects. It implements the Scanner interface, which accepts a Reader + object to fetch characters from and a ScanHandler object to report lexical + events to. + */ +public class HTMLScanner implements Scanner, Locator { + // Start of state table + private static final int S_ANAME = 1; + private static final int S_APOS = 2; + private static final int S_AVAL = 3; + private static final int S_BB = 4; + private static final int S_BBC = 5; + private static final int S_BBCD = 6; + private static final int S_BBCDA = 7; + private static final int S_BBCDAT = 8; + private static final int S_BBCDATA = 9; + private static final int S_CDATA = 10; + private static final int S_CDATA2 = 11; + private static final int S_CDSECT = 12; + private static final int S_CDSECT1 = 13; + private static final int S_CDSECT2 = 14; + private static final int S_COM = 15; + private static final int S_COM2 = 16; + private static final int S_COM3 = 17; + private static final int S_COM4 = 18; + private static final int S_DECL = 19; + private static final int S_DECL2 = 20; + private static final int S_DONE = 21; + private static final int S_EMPTYTAG = 22; + private static final int S_ENT = 23; + private static final int S_EQ = 24; + private static final int S_ETAG = 25; + private static final int S_GI = 26; + private static final int S_NCR = 27; + private static final int S_PCDATA = 28; + private static final int S_PI = 29; + private static final int S_PITARGET = 30; + private static final int S_QUOT = 31; + private static final int S_STAGC = 32; + private static final int S_TAG = 33; + private static final int S_TAGWS = 34; + private static final int S_XNCR = 35; + private static final int A_ADUP = 1; + private static final int A_ADUP_SAVE = 2; + private static final int A_ADUP_STAGC = 3; + private static final int A_ANAME = 4; + private static final int A_ANAME_ADUP = 5; + private static final int A_ANAME_ADUP_STAGC = 6; + private static final int A_AVAL = 7; + private static final int A_AVAL_STAGC = 8; + private static final int A_CDATA = 9; + private static final int A_CMNT = 10; + private static final int A_DECL = 11; + private static final int A_EMPTYTAG = 12; + private static final int A_ENTITY = 13; + private static final int A_ENTITY_START = 14; + private static final int A_ETAG = 15; + private static final int A_GI = 16; + private static final int A_GI_STAGC = 17; + private static final int A_LT = 18; + private static final int A_LT_PCDATA = 19; + private static final int A_MINUS = 20; + private static final int A_MINUS2 = 21; + private static final int A_MINUS3 = 22; + private static final int A_PCDATA = 23; + private static final int A_PI = 24; + private static final int A_PITARGET = 25; + private static final int A_PITARGET_PI = 26; + private static final int A_SAVE = 27; + private static final int A_SKIP = 28; + private static final int A_SP = 29; + private static final int A_STAGC = 30; + private static final int A_UNGET = 31; + private static final int A_UNSAVE_PCDATA = 32; + private static int[] statetable = { + S_ANAME, '/', A_ANAME_ADUP, S_EMPTYTAG, + S_ANAME, '=', A_ANAME, S_AVAL, + S_ANAME, '>', A_ANAME_ADUP_STAGC, S_PCDATA, + S_ANAME, 0, A_SAVE, S_ANAME, + S_ANAME, -1, A_ANAME_ADUP_STAGC, S_DONE, + S_ANAME, ' ', A_ANAME, S_EQ, + S_ANAME, '\n', A_ANAME, S_EQ, + S_ANAME, '\t', A_ANAME, S_EQ, + S_APOS, '\'', A_AVAL, S_TAGWS, + S_APOS, 0, A_SAVE, S_APOS, + S_APOS, -1, A_AVAL_STAGC, S_DONE, + S_APOS, ' ', A_SP, S_APOS, + S_APOS, '\n', A_SP, S_APOS, + S_APOS, '\t', A_SP, S_APOS, + S_AVAL, '"', A_SKIP, S_QUOT, + S_AVAL, '\'', A_SKIP, S_APOS, + S_AVAL, '>', A_AVAL_STAGC, S_PCDATA, + S_AVAL, 0, A_SAVE, S_STAGC, + S_AVAL, -1, A_AVAL_STAGC, S_DONE, + S_AVAL, ' ', A_SKIP, S_AVAL, + S_AVAL, '\n', A_SKIP, S_AVAL, + S_AVAL, '\t', A_SKIP, S_AVAL, + S_BB, 'C', A_SKIP, S_BBC, + S_BB, 0, A_SKIP, S_DECL, + S_BB, -1, A_SKIP, S_DONE, + S_BBC, 'D', A_SKIP, S_BBCD, + S_BBC, 0, A_SKIP, S_DECL, + S_BBC, -1, A_SKIP, S_DONE, + S_BBCD, 'A', A_SKIP, S_BBCDA, + S_BBCD, 0, A_SKIP, S_DECL, + S_BBCD, -1, A_SKIP, S_DONE, + S_BBCDA, 'T', A_SKIP, S_BBCDAT, + S_BBCDA, 0, A_SKIP, S_DECL, + S_BBCDA, -1, A_SKIP, S_DONE, + S_BBCDAT, 'A', A_SKIP, S_BBCDATA, + S_BBCDAT, 0, A_SKIP, S_DECL, + S_BBCDAT, -1, A_SKIP, S_DONE, + S_BBCDATA, '[', A_SKIP, S_CDSECT, + S_BBCDATA, 0, A_SKIP, S_DECL, + S_BBCDATA, -1, A_SKIP, S_DONE, + S_CDATA, '<', A_SAVE, S_CDATA2, + S_CDATA, 0, A_SAVE, S_CDATA, + S_CDATA, -1, A_PCDATA, S_DONE, + S_CDATA2, '/', A_UNSAVE_PCDATA, S_ETAG, + S_CDATA2, 0, A_SAVE, S_CDATA, + S_CDATA2, -1, A_UNSAVE_PCDATA, S_DONE, + S_CDSECT, ']', A_SAVE, S_CDSECT1, + S_CDSECT, 0, A_SAVE, S_CDSECT, + S_CDSECT, -1, A_SKIP, S_DONE, + S_CDSECT1, ']', A_SAVE, S_CDSECT2, + S_CDSECT1, 0, A_SAVE, S_CDSECT, + S_CDSECT1, -1, A_SKIP, S_DONE, + S_CDSECT2, '>', A_CDATA, S_PCDATA, + S_CDSECT2, 0, A_SAVE, S_CDSECT, + S_CDSECT2, -1, A_SKIP, S_DONE, + S_COM, '-', A_SKIP, S_COM2, + S_COM, 0, A_SAVE, S_COM2, + S_COM, -1, A_CMNT, S_DONE, + S_COM2, '-', A_SKIP, S_COM3, + S_COM2, 0, A_SAVE, S_COM2, + S_COM2, -1, A_CMNT, S_DONE, + S_COM3, '-', A_SKIP, S_COM4, + S_COM3, 0, A_MINUS, S_COM2, + S_COM3, -1, A_CMNT, S_DONE, + S_COM4, '-', A_MINUS3, S_COM4, + S_COM4, '>', A_CMNT, S_PCDATA, + S_COM4, 0, A_MINUS2, S_COM2, + S_COM4, -1, A_CMNT, S_DONE, + S_DECL, '-', A_SKIP, S_COM, + S_DECL, '>', A_SKIP, S_PCDATA, + S_DECL, '[', A_SKIP, S_BB, + S_DECL, 0, A_SAVE, S_DECL2, + S_DECL, -1, A_SKIP, S_DONE, + S_DECL2, '>', A_DECL, S_PCDATA, + S_DECL2, 0, A_SAVE, S_DECL2, + S_DECL2, -1, A_SKIP, S_DONE, + S_EMPTYTAG, '>', A_EMPTYTAG, S_PCDATA, + S_EMPTYTAG, 0, A_SAVE, S_ANAME, + S_EMPTYTAG, ' ', A_SKIP, S_TAGWS, + S_EMPTYTAG, '\n', A_SKIP, S_TAGWS, + S_EMPTYTAG, '\t', A_SKIP, S_TAGWS, + S_ENT, 0, A_ENTITY, S_ENT, + S_ENT, -1, A_ENTITY, S_DONE, + S_EQ, '=', A_SKIP, S_AVAL, + S_EQ, '>', A_ADUP_STAGC, S_PCDATA, + S_EQ, 0, A_ADUP_SAVE, S_ANAME, + S_EQ, -1, A_ADUP_STAGC, S_DONE, + S_EQ, ' ', A_SKIP, S_EQ, + S_EQ, '\n', A_SKIP, S_EQ, + S_EQ, '\t', A_SKIP, S_EQ, + S_ETAG, '>', A_ETAG, S_PCDATA, + S_ETAG, 0, A_SAVE, S_ETAG, + S_ETAG, -1, A_ETAG, S_DONE, + S_ETAG, ' ', A_SKIP, S_ETAG, + S_ETAG, '\n', A_SKIP, S_ETAG, + S_ETAG, '\t', A_SKIP, S_ETAG, + S_GI, '/', A_SKIP, S_EMPTYTAG, + S_GI, '>', A_GI_STAGC, S_PCDATA, + S_GI, 0, A_SAVE, S_GI, + S_GI, -1, A_SKIP, S_DONE, + S_GI, ' ', A_GI, S_TAGWS, + S_GI, '\n', A_GI, S_TAGWS, + S_GI, '\t', A_GI, S_TAGWS, + S_NCR, 0, A_ENTITY, S_NCR, + S_NCR, -1, A_ENTITY, S_DONE, + S_PCDATA, '&', A_ENTITY_START, S_ENT, + S_PCDATA, '<', A_PCDATA, S_TAG, + S_PCDATA, 0, A_SAVE, S_PCDATA, + S_PCDATA, -1, A_PCDATA, S_DONE, + S_PI, '>', A_PI, S_PCDATA, + S_PI, 0, A_SAVE, S_PI, + S_PI, -1, A_PI, S_DONE, + S_PITARGET, '>', A_PITARGET_PI, S_PCDATA, + S_PITARGET, 0, A_SAVE, S_PITARGET, + S_PITARGET, -1, A_PITARGET_PI, S_DONE, + S_PITARGET, ' ', A_PITARGET, S_PI, + S_PITARGET, '\n', A_PITARGET, S_PI, + S_PITARGET, '\t', A_PITARGET, S_PI, + S_QUOT, '"', A_AVAL, S_TAGWS, + S_QUOT, 0, A_SAVE, S_QUOT, + S_QUOT, -1, A_AVAL_STAGC, S_DONE, + S_QUOT, ' ', A_SP, S_QUOT, + S_QUOT, '\n', A_SP, S_QUOT, + S_QUOT, '\t', A_SP, S_QUOT, + S_STAGC, '>', A_AVAL_STAGC, S_PCDATA, + S_STAGC, 0, A_SAVE, S_STAGC, + S_STAGC, -1, A_AVAL_STAGC, S_DONE, + S_STAGC, ' ', A_AVAL, S_TAGWS, + S_STAGC, '\n', A_AVAL, S_TAGWS, + S_STAGC, '\t', A_AVAL, S_TAGWS, + S_TAG, '!', A_SKIP, S_DECL, + S_TAG, '/', A_SKIP, S_ETAG, + S_TAG, '<', A_SAVE, S_TAG, + S_TAG, '?', A_SKIP, S_PITARGET, + S_TAG, 0, A_SAVE, S_GI, + S_TAG, -1, A_LT_PCDATA, S_DONE, + S_TAG, ' ', A_LT, S_PCDATA, + S_TAG, '\n', A_LT, S_PCDATA, + S_TAG, '\t', A_LT, S_PCDATA, + S_TAGWS, '/', A_SKIP, S_EMPTYTAG, + S_TAGWS, '>', A_STAGC, S_PCDATA, + S_TAGWS, 0, A_SAVE, S_ANAME, + S_TAGWS, -1, A_STAGC, S_DONE, + S_TAGWS, ' ', A_SKIP, S_TAGWS, + S_TAGWS, '\n', A_SKIP, S_TAGWS, + S_TAGWS, '\t', A_SKIP, S_TAGWS, + S_XNCR, 0, A_ENTITY, S_XNCR, + S_XNCR, -1, A_ENTITY, S_DONE, + }; + private static final String[] debug_actionnames = { "", "A_ADUP", "A_ADUP_SAVE", "A_ADUP_STAGC", "A_ANAME", "A_ANAME_ADUP", "A_ANAME_ADUP_STAGC", "A_AVAL", "A_AVAL_STAGC", "A_CDATA", "A_CMNT", "A_DECL", "A_EMPTYTAG", "A_ENTITY", "A_ENTITY_START", "A_ETAG", "A_GI", "A_GI_STAGC", "A_LT", "A_LT_PCDATA", "A_MINUS", "A_MINUS2", "A_MINUS3", "A_PCDATA", "A_PI", "A_PITARGET", "A_PITARGET_PI", "A_SAVE", "A_SKIP", "A_SP", "A_STAGC", "A_UNGET", "A_UNSAVE_PCDATA"}; + private static final String[] debug_statenames = { "", "S_ANAME", "S_APOS", "S_AVAL", "S_BB", "S_BBC", "S_BBCD", "S_BBCDA", "S_BBCDAT", "S_BBCDATA", "S_CDATA", "S_CDATA2", "S_CDSECT", "S_CDSECT1", "S_CDSECT2", "S_COM", "S_COM2", "S_COM3", "S_COM4", "S_DECL", "S_DECL2", "S_DONE", "S_EMPTYTAG", "S_ENT", "S_EQ", "S_ETAG", "S_GI", "S_NCR", "S_PCDATA", "S_PI", "S_PITARGET", "S_QUOT", "S_STAGC", "S_TAG", "S_TAGWS", "S_XNCR"}; + // End of state table + private String thePublicid; // Locator state + private String theSystemid; + private int theLastLine; + private int theLastColumn; + private int theCurrentLine; + private int theCurrentColumn; + int theState; // Current state + int theNextState; // Next state + char[] theOutputBuffer = new char[200]; // Output buffer + int theSize; // Current buffer size + int[] theWinMap = { // Windows chars map + 0x20AC, 0xFFFD, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, + 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0xFFFD, 0x017D, 0xFFFD, + 0xFFFD, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, + 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0xFFFD, 0x017E, 0x0178}; + /** + * Index into the state table for [state][input character - 2]. + * The state table consists of 4-entry runs on the form + * { current state, input character, action, next state }. + * We precompute the index into the state table for all possible + * { current state, input character } and store the result in + * the statetableIndex array. Since only some input characters + * are present in the state table, we only do the computation for + * characters 0 to the highest character value in the state table. + * An input character of -2 is used to cover all other characters + * as -2 is guaranteed not to match any input character entry + * in the state table. + * + *

When doing lookups, the input character should first be tested + * to be in the range [-1 (inclusive), statetableIndexMaxChar (exclusive)]. + * if it isn't use -2 as the input character. + * + *

Finally, add 2 to the input character to cover for the fact that + * Java doesn't support negative array indexes. Then look up + * the value in the statetableIndex. If the value is -1, then + * no action or next state was found for the { state, input } that + * you had. If it isn't -1, then action = statetable[value + 2] and + * next state = statetable[value + 3]. That is, the value points + * to the start of the answer 4-tuple in the statetable. + */ + static short[][] statetableIndex; + /** + * The highest character value seen in the statetable. + * See the doc comment for statetableIndex to see how this + * is used. + */ + static int statetableIndexMaxChar; + static { + int maxState = -1; + int maxChar = -1; + for (int i = 0; i < statetable.length; i += 4) { + if (statetable[i] > maxState) { + maxState = statetable[i]; + } + if (statetable[i + 1] > maxChar) { + maxChar = statetable[i + 1]; + } + } + statetableIndexMaxChar = maxChar + 1; + statetableIndex = new short[maxState + 1][maxChar + 3]; + for (int theState = 0; theState <= maxState; ++theState) { + for (int ch = -2; ch <= maxChar; ++ch) { + int hit = -1; + int action = 0; + for (int i = 0; i < statetable.length; i += 4) { + if (theState != statetable[i]) { + if (action != 0) break; + continue; + } + if (statetable[i+1] == 0) { + hit = i; + action = statetable[i+2]; + } + else if (statetable[i+1] == ch) { + hit = i; + action = statetable[i+2]; + break; + } + } + statetableIndex[theState][ch + 2] = (short) hit; + } + } + } + // Compensate for bug in PushbackReader that allows + // pushing back EOF. + private void unread(PushbackReader r, int c) throws IOException { + if (c != -1) r.unread(c); + } + // Locator implementation + public int getLineNumber() { + return theLastLine; + } + public int getColumnNumber() { + return theLastColumn; + } + public String getPublicId() { + return thePublicid; + } + public String getSystemId() { + return theSystemid; + } + // Scanner implementation + /** + Reset document locator, supplying systemid and publicid. + @param systemid System id + @param publicid Public id + */ + public void resetDocumentLocator(String publicid, String systemid) { + thePublicid = publicid; + theSystemid = systemid; + theLastLine = theLastColumn = theCurrentLine = theCurrentColumn = 0; + } + /** + Scan HTML source, reporting lexical events. + @param r0 Reader that provides characters + @param h ScanHandler that accepts lexical events. + */ + public void scan(Reader r0, ScanHandler h) throws IOException, SAXException { + theState = S_PCDATA; + PushbackReader r; + if (r0 instanceof BufferedReader) { + r = new PushbackReader(r0, 5); + } + else { + r = new PushbackReader(new BufferedReader(r0), 5); + } + int firstChar = r.read(); // Remove any leading BOM + if (firstChar != '\uFEFF') unread(r, firstChar); + while (theState != S_DONE) { + int ch = r.read(); + // Process control characters + if (ch >= 0x80 && ch <= 0x9F) ch = theWinMap[ch-0x80]; + if (ch == '\r') { + ch = r.read(); // expect LF next + if (ch != '\n') { + unread(r, ch); // nope + ch = '\n'; + } + } + if (ch == '\n') { + theCurrentLine++; + theCurrentColumn = 0; + } + else { + theCurrentColumn++; + } + if (!(ch >= 0x20 || ch == '\n' || ch == '\t' || ch == -1)) continue; + // Search state table + int adjCh = (ch >= -1 && ch < statetableIndexMaxChar) ? ch : -2; + int statetableRow = statetableIndex[theState][adjCh + 2]; + int action = 0; + if (statetableRow != -1) { + action = statetable[statetableRow + 2]; + theNextState = statetable[statetableRow + 3]; + } +// System.err.println("In " + debug_statenames[theState] + " got " + nicechar(ch) + " doing " + debug_actionnames[action] + " then " + debug_statenames[theNextState]); + switch (action) { + case 0: + throw new Error( + "HTMLScanner can't cope with " + Integer.toString(ch) + " in state " + + Integer.toString(theState)); + case A_ADUP: + h.adup(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_ADUP_SAVE: + h.adup(theOutputBuffer, 0, theSize); + theSize = 0; + save(ch, h); + break; + case A_ADUP_STAGC: + h.adup(theOutputBuffer, 0, theSize); + theSize = 0; + h.stagc(theOutputBuffer, 0, theSize); + break; + case A_ANAME: + h.aname(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_ANAME_ADUP: + h.aname(theOutputBuffer, 0, theSize); + theSize = 0; + h.adup(theOutputBuffer, 0, theSize); + break; + case A_ANAME_ADUP_STAGC: + h.aname(theOutputBuffer, 0, theSize); + theSize = 0; + h.adup(theOutputBuffer, 0, theSize); + h.stagc(theOutputBuffer, 0, theSize); + break; + case A_AVAL: + h.aval(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_AVAL_STAGC: + h.aval(theOutputBuffer, 0, theSize); + theSize = 0; + h.stagc(theOutputBuffer, 0, theSize); + break; + case A_CDATA: + mark(); + // suppress the final "]]" in the buffer + if (theSize > 1) theSize -= 2; + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_ENTITY_START: + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + save(ch, h); + break; + case A_ENTITY: + mark(); + char ch1 = (char)ch; +// System.out.println("Got " + ch1 + " in state " + ((theState == S_ENT) ? "S_ENT" : ((theState == S_NCR) ? "S_NCR" : "UNK"))); + if (theState == S_ENT && ch1 == '#') { + theNextState = S_NCR; + save(ch, h); + break; + } + else if (theState == S_NCR && (ch1 == 'x' || ch1 == 'X')) { + theNextState = S_XNCR; + save(ch, h); + break; + } + else if (theState == S_ENT && Character.isLetterOrDigit(ch1)) { + save(ch, h); + break; + } + else if (theState == S_NCR && Character.isDigit(ch1)) { + save(ch, h); + break; + } + else if (theState == S_XNCR && (Character.isDigit(ch1) || "abcdefABCDEF".indexOf(ch1) != -1)) { + save(ch, h); + break; + } + // The whole entity reference has been collected +// System.err.println("%%" + new String(theOutputBuffer, 0, theSize)); + h.entity(theOutputBuffer, 1, theSize - 1); + int ent = h.getEntity(); +// System.err.println("%% value = " + ent); + if (ent != 0) { + theSize = 0; + if (ent >= 0x80 && ent <= 0x9F) { + ent = theWinMap[ent-0x80]; + } + if (ent < 0x20) { + // Control becomes space + ent = 0x20; + } + else if (ent >= 0xD800 && ent <= 0xDFFF) { + // Surrogates get dropped + ent = 0; + } + else if (ent <= 0xFFFF) { + // BMP character + save(ent, h); + } + else { + // Astral converted to two surrogates + ent -= 0x10000; + save((ent>>10) + 0xD800, h); + save((ent&0x3FF) + 0xDC00, h); + } + if (ch != ';') { + unread(r, ch); + theCurrentColumn--; + } + } + else { + unread(r, ch); + theCurrentColumn--; + } + theNextState = S_PCDATA; + break; + case A_ETAG: + h.etag(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_DECL: + h.decl(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_GI: + h.gi(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_GI_STAGC: + h.gi(theOutputBuffer, 0, theSize); + theSize = 0; + h.stagc(theOutputBuffer, 0, theSize); + break; + case A_LT: + mark(); + save('<', h); + save(ch, h); + break; + case A_LT_PCDATA: + mark(); + save('<', h); + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_PCDATA: + mark(); + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_CMNT: + mark(); + h.cmnt(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_MINUS3: + save('-', h); + save(' ', h); + break; + case A_MINUS2: + save('-', h); + save(' ', h); + // fall through into A_MINUS + case A_MINUS: + save('-', h); + save(ch, h); + break; + case A_PI: + mark(); + h.pi(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_PITARGET: + h.pitarget(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_PITARGET_PI: + h.pitarget(theOutputBuffer, 0, theSize); + theSize = 0; + h.pi(theOutputBuffer, 0, theSize); + break; + case A_SAVE: + save(ch, h); + break; + case A_SKIP: + break; + case A_SP: + save(' ', h); + break; + case A_STAGC: + h.stagc(theOutputBuffer, 0, theSize); + theSize = 0; + break; + case A_EMPTYTAG: + mark(); +// System.err.println("%%% Empty tag seen"); + if (theSize > 0) h.gi(theOutputBuffer, 0, theSize); + theSize = 0; + h.stage(theOutputBuffer, 0, theSize); + break; + case A_UNGET: + unread(r, ch); + theCurrentColumn--; + break; + case A_UNSAVE_PCDATA: + if (theSize > 0) theSize--; + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + break; + default: + throw new Error("Can't process state " + action); + } + theState = theNextState; + } + h.eof(theOutputBuffer, 0, 0); + } + /** + * Mark the current scan position as a "point of interest" - start of a tag, + * cdata, processing instruction etc. + */ + private void mark() { + theLastColumn = theCurrentColumn; + theLastLine = theCurrentLine; + } + /** + A callback for the ScanHandler that allows it to force + the lexer state to CDATA content (no markup is recognized except + the end of element. + */ + public void startCDATA() { theNextState = S_CDATA; } + private void save(int ch, ScanHandler h) throws IOException, SAXException { + if (theSize >= theOutputBuffer.length - 20) { + if (theState == S_PCDATA || theState == S_CDATA) { + // Return a buffer-sized chunk of PCDATA + h.pcdata(theOutputBuffer, 0, theSize); + theSize = 0; + } + else { + // Grow the buffer size + char[] newOutputBuffer = new char[theOutputBuffer.length * 2]; + System.arraycopy(theOutputBuffer, 0, newOutputBuffer, 0, theSize+1); + theOutputBuffer = newOutputBuffer; + } + } + theOutputBuffer[theSize++] = (char)ch; + } + /** + Test procedure. Reads HTML from the standard input and writes + PYX to the standard output. + */ + public static void main(String[] argv) throws IOException, SAXException { + Scanner s = new HTMLScanner(); + Reader r = new InputStreamReader(System.in, "UTF-8"); + Writer w = new OutputStreamWriter(System.out, "UTF-8"); + PYXWriter pw = new PYXWriter(w); + s.scan(r, pw); + w.close(); + } + private static String nicechar(int in) { + if (in == '\n') return "\\n"; + if (in < 32) return "0x"+Integer.toHexString(in); + return "'"+((char)in)+"'"; + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLSchema.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLSchema.java new file mode 100755 index 0000000000..b5360ef65a --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/HTMLSchema.java @@ -0,0 +1,2907 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +/** + This class provides a Schema that has been preinitialized with HTML + elements, attributes, and character entity declarations. All the declarations + normally provided with HTML 4.01 are given, plus some that are IE-specific + and NS4-specific. Attribute declarations of type CDATA with no default + value are not included. + */ +package org.ccil.cowan.tagsoup; +public class HTMLSchema extends Schema implements HTMLModels { + /** + Returns a newly constructed HTMLSchema object independent of + any existing ones. + */ + public HTMLSchema() { + // Start of Schema calls + setURI("http://www.w3.org/1999/xhtml"); + setPrefix("html"); + elementType("", M_EMPTY, M_PCDATA, 0); + elementType("", M_ROOT, M_EMPTY, 0); + elementType("a", M_PCDATA|M_NOLINK, M_INLINE, 0); + elementType("abbr", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("acronym", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("address", M_PCDATA|M_INLINE|M_P, M_BLOCK, 0); + elementType("applet", M_PCDATA|M_PARAM|M_INLINE|M_BLOCK, M_INLINE|M_NOLINK, 0); + elementType("area", M_EMPTY, M_AREA, 0); + elementType("b", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("base", M_EMPTY, M_HEAD, 0); + elementType("basefont", M_EMPTY, M_INLINE|M_NOLINK, 0); + elementType("bdo", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("bgsound", M_EMPTY, M_HEAD, 0); + elementType("big", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("blink", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("blockquote", M_PCDATA|M_INLINE|M_BLOCK, M_BLOCK, 0); + elementType("body", M_PCDATA|M_INLINE|M_BLOCK, M_HTML|M_BODY, 0); + elementType("br", M_EMPTY, M_INLINE|M_NOLINK, 0); + elementType("button", M_PCDATA|M_INLINE|M_BLOCK, M_INLINE|M_NOLINK, 0); + elementType("canvas", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("caption", M_PCDATA|M_INLINE, M_TABULAR, 0); + elementType("center", M_PCDATA|M_INLINE|M_BLOCK, M_BLOCK, 0); + elementType("cite", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("code", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("col", M_EMPTY, M_COL|M_TABULAR, 0); + elementType("colgroup", M_COL, M_TABULAR, 0); + elementType("comment", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("dd", M_PCDATA|M_INLINE|M_BLOCK, M_DEF, 0); + elementType("del", M_PCDATA|M_INLINE|M_BLOCK, M_INLINE|M_BLOCKINLINE|M_BLOCK, F_RESTART); + elementType("dfn", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("dir", M_LI, M_BLOCK, 0); + elementType("div", M_PCDATA|M_INLINE|M_BLOCK, M_BLOCK, 0); + elementType("dl", M_DEF, M_BLOCK, 0); + elementType("dt", M_PCDATA|M_INLINE, M_DEF, 0); + elementType("em", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("fieldset", M_PCDATA|M_LEGEND|M_INLINE|M_BLOCK, M_BLOCK, 0); + elementType("font", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("form", M_PCDATA|M_INLINE|M_NOLINK|M_BLOCK|M_TR|M_CELL, M_BLOCK|M_FORM, F_NOFORCE); + elementType("frame", M_EMPTY, M_FRAME, 0); + elementType("frameset", M_FRAME, M_FRAME|M_HTML, 0); + elementType("h1", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("h2", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("h3", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("h4", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("h5", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("h6", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("head", M_HEAD, M_HTML, 0); + elementType("hr", M_EMPTY, M_BLOCK, 0); + elementType("html", M_HTML, M_ROOT, 0); + elementType("i", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("iframe", M_PCDATA|M_INLINE|M_BLOCK, M_INLINE|M_NOLINK, 0); + elementType("img", M_EMPTY, M_INLINE|M_NOLINK, 0); + elementType("input", M_EMPTY, M_INLINE|M_NOLINK, 0); + elementType("ins", M_PCDATA|M_INLINE|M_BLOCK, M_INLINE|M_BLOCK, F_RESTART); + elementType("isindex", M_EMPTY, M_HEAD, 0); + elementType("kbd", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("label", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("legend", M_PCDATA|M_INLINE, M_LEGEND, 0); + elementType("li", M_PCDATA|M_INLINE|M_BLOCK, M_LI, 0); + elementType("link", M_EMPTY, M_HEAD|M_INLINE, 0); + elementType("listing", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("map", M_BLOCK|M_AREA, M_INLINE, 0); + elementType("marquee", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("menu", M_LI, M_BLOCK, 0); + elementType("meta", M_EMPTY, M_HEAD, 0); + elementType("nobr", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("noframes", M_BODY|M_BLOCK|M_INLINE, M_BLOCK|M_HTML|M_FRAME, 0); + elementType("noscript", M_PCDATA|M_INLINE|M_BLOCK, M_BLOCK, 0); + elementType("object", M_PCDATA|M_PARAM|M_INLINE|M_BLOCK, M_HEAD|M_INLINE|M_NOLINK, 0); + elementType("ol", M_LI, M_BLOCK, 0); + elementType("optgroup", M_OPTIONS, M_OPTIONS, 0); + elementType("option", M_PCDATA, M_OPTION|M_OPTIONS, 0); + elementType("p", M_PCDATA|M_INLINE|M_TABLE, M_BLOCK|M_P, 0); + elementType("param", M_EMPTY, M_PARAM, 0); + elementType("pre", M_PCDATA|M_INLINE, M_BLOCK, 0); + elementType("q", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("rb", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("rbc", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("rp", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("rt", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("rtc", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("ruby", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("s", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("samp", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("script", M_PCDATA, M_ANY & ~M_ROOT, F_CDATA); + elementType("select", M_OPTIONS, M_INLINE, 0); + elementType("small", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("span", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("strike", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("strong", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("style", M_PCDATA, M_HEAD|M_INLINE, F_CDATA); + elementType("sub", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("sup", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("table", M_FORM|M_TABULAR, M_BLOCK|M_TABLE, F_NOFORCE); + elementType("tbody", M_TR, M_TABULAR, 0); + elementType("td", M_PCDATA|M_INLINE|M_BLOCK, M_CELL, 0); + elementType("textarea", M_PCDATA, M_INLINE, 0); + elementType("tfoot", M_TR|M_FORM|M_CELL, M_TABULAR, 0); + elementType("th", M_PCDATA|M_INLINE|M_BLOCK, M_CELL, 0); + elementType("thead", M_TR|M_FORM|M_CELL, M_TABULAR, 0); + elementType("title", M_PCDATA, M_HEAD, 0); + elementType("tr", M_FORM|M_CELL, M_TR|M_TABULAR, 0); + elementType("tt", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("u", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, F_RESTART); + elementType("ul", M_LI, M_BLOCK, 0); + elementType("var", M_PCDATA|M_INLINE, M_INLINE|M_NOLINK, 0); + elementType("wbr", M_EMPTY, M_INLINE|M_NOLINK, 0); + elementType("xmp", M_PCDATA|M_INLINE, M_BLOCK, 0); + parent("", "body"); + parent("html", ""); + parent("a", "body"); + parent("abbr", "body"); + parent("acronym", "body"); + parent("address", "body"); + parent("applet", "body"); + parent("area", "map"); + parent("b", "body"); + parent("base", "head"); + parent("basefont", "body"); + parent("bdo", "body"); + parent("bgsound", "head"); + parent("big", "body"); + parent("blink", "body"); + parent("blockquote", "body"); + parent("body", "html"); + parent("br", "body"); + parent("button", "form"); + parent("canvas", "body"); + parent("caption", "table"); + parent("center", "body"); + parent("cite", "body"); + parent("code", "body"); + parent("col", "table"); + parent("colgroup", "table"); + parent("comment", "body"); + parent("dd", "dl"); + parent("del", "body"); + parent("dfn", "body"); + parent("dir", "body"); + parent("div", "body"); + parent("dl", "body"); + parent("dt", "dl"); + parent("em", "body"); + parent("fieldset", "form"); + parent("font", "body"); + parent("form", "body"); + parent("frame", "frameset"); + parent("frameset", "html"); + parent("h1", "body"); + parent("h2", "body"); + parent("h3", "body"); + parent("h4", "body"); + parent("h5", "body"); + parent("h6", "body"); + parent("head", "html"); + parent("hr", "body"); + parent("i", "body"); + parent("iframe", "body"); + parent("img", "body"); + parent("input", "form"); + parent("ins", "body"); + parent("isindex", "head"); + parent("kbd", "body"); + parent("label", "form"); + parent("legend", "fieldset"); + parent("li", "ul"); + parent("link", "head"); + parent("listing", "body"); + parent("map", "body"); + parent("marquee", "body"); + parent("menu", "body"); + parent("meta", "head"); + parent("nobr", "body"); + parent("noframes", "html"); + parent("noscript", "body"); + parent("object", "body"); + parent("ol", "body"); + parent("optgroup", "select"); + parent("option", "select"); + parent("p", "body"); + parent("param", "object"); + parent("pre", "body"); + parent("q", "body"); + parent("rb", "body"); + parent("rbc", "body"); + parent("rp", "body"); + parent("rt", "body"); + parent("rtc", "body"); + parent("ruby", "body"); + parent("s", "body"); + parent("samp", "body"); + parent("script", "html"); + parent("select", "form"); + parent("small", "body"); + parent("span", "body"); + parent("strike", "body"); + parent("strong", "body"); + parent("style", "head"); + parent("sub", "body"); + parent("sup", "body"); + parent("table", "body"); + parent("tbody", "table"); + parent("td", "tr"); + parent("textarea", "form"); + parent("tfoot", "table"); + parent("th", "tr"); + parent("thead", "table"); + parent("title", "head"); + parent("tr", "tbody"); + parent("tt", "body"); + parent("u", "body"); + parent("ul", "body"); + parent("var", "body"); + parent("wbr", "body"); + parent("xmp", "body"); + attribute("a", "hreflang", "NMTOKEN", null); + attribute("a", "shape", "CDATA", "rect"); + attribute("a", "tabindex", "NMTOKEN", null); + attribute("applet", "align", "NMTOKEN", null); + attribute("area", "nohref", "BOOLEAN", null); + attribute("area", "shape", "CDATA", "rect"); + attribute("area", "tabindex", "NMTOKEN", null); + attribute("br", "clear", "CDATA", "none"); + attribute("button", "disabled", "BOOLEAN", null); + attribute("button", "tabindex", "NMTOKEN", null); + attribute("button", "type", "CDATA", "submit"); + attribute("caption", "align", "NMTOKEN", null); + attribute("col", "align", "NMTOKEN", null); + attribute("col", "span", "CDATA", "1"); + attribute("col", "valign", "NMTOKEN", null); + attribute("colgroup", "align", "NMTOKEN", null); + attribute("colgroup", "span", "CDATA", "1"); + attribute("colgroup", "valign", "NMTOKEN", null); + attribute("dir", "compact", "BOOLEAN", null); + attribute("div", "align", "NMTOKEN", null); + attribute("dl", "compact", "BOOLEAN", null); + attribute("form", "enctype", "CDATA", "application/x-www-form-urlencoded"); + attribute("form", "method", "CDATA", "get"); + attribute("frame", "frameborder", "CDATA", "1"); + attribute("frame", "noresize", "BOOLEAN", null); + attribute("frame", "scrolling", "CDATA", "auto"); + attribute("h1", "align", "NMTOKEN", null); + attribute("h2", "align", "NMTOKEN", null); + attribute("h3", "align", "NMTOKEN", null); + attribute("h4", "align", "NMTOKEN", null); + attribute("h5", "align", "NMTOKEN", null); + attribute("h6", "align", "NMTOKEN", null); + attribute("hr", "align", "NMTOKEN", null); + attribute("hr", "noshade", "BOOLEAN", null); + attribute("iframe", "align", "NMTOKEN", null); + attribute("iframe", "frameborder", "CDATA", "1"); + attribute("iframe", "scrolling", "CDATA", "auto"); + attribute("img", "align", "NMTOKEN", null); + attribute("img", "ismap", "BOOLEAN", null); + attribute("input", "align", "NMTOKEN", null); + attribute("input", "checked", "BOOLEAN", null); + attribute("input", "disabled", "BOOLEAN", null); + attribute("input", "ismap", "BOOLEAN", null); + attribute("input", "maxlength", "NMTOKEN", null); + attribute("input", "readonly", "BOOLEAN", null); + attribute("input", "tabindex", "NMTOKEN", null); + attribute("input", "type", "CDATA", "text"); + attribute("label", "for", "IDREF", null); + attribute("legend", "align", "NMTOKEN", null); + attribute("li", "value", "NMTOKEN", null); + attribute("link", "hreflang", "NMTOKEN", null); + attribute("marquee", "width", "NMTOKEN", null); + attribute("menu", "compact", "BOOLEAN", null); + attribute("meta", "http-equiv", "NMTOKEN", null); + attribute("meta", "name", "NMTOKEN", null); + attribute("object", "align", "NMTOKEN", null); + attribute("object", "declare", "BOOLEAN", null); + attribute("object", "tabindex", "NMTOKEN", null); + attribute("ol", "compact", "BOOLEAN", null); + attribute("ol", "start", "NMTOKEN", null); + attribute("optgroup", "disabled", "BOOLEAN", null); + attribute("option", "disabled", "BOOLEAN", null); + attribute("option", "selected", "BOOLEAN", null); + attribute("p", "align", "NMTOKEN", null); + attribute("param", "valuetype", "CDATA", "data"); + attribute("pre", "width", "NMTOKEN", null); + attribute("rt", "rbspan", "CDATA", "1"); + attribute("script", "defer", "BOOLEAN", null); + attribute("select", "disabled", "BOOLEAN", null); + attribute("select", "multiple", "BOOLEAN", null); + attribute("select", "size", "NMTOKEN", null); + attribute("select", "tabindex", "NMTOKEN", null); + attribute("table", "align", "NMTOKEN", null); + attribute("table", "frame", "NMTOKEN", null); + attribute("table", "rules", "NMTOKEN", null); + attribute("tbody", "align", "NMTOKEN", null); + attribute("tbody", "valign", "NMTOKEN", null); + attribute("td", "align", "NMTOKEN", null); + attribute("td", "colspan", "CDATA", "1"); + attribute("td", "headers", "IDREFS", null); + attribute("td", "nowrap", "BOOLEAN", null); + attribute("td", "rowspan", "CDATA", "1"); + attribute("td", "scope", "NMTOKEN", null); + attribute("td", "valign", "NMTOKEN", null); + attribute("textarea", "cols", "NMTOKEN", null); + attribute("textarea", "disabled", "BOOLEAN", null); + attribute("textarea", "readonly", "BOOLEAN", null); + attribute("textarea", "rows", "NMTOKEN", null); + attribute("textarea", "tabindex", "NMTOKEN", null); + attribute("tfoot", "align", "NMTOKEN", null); + attribute("tfoot", "valign", "NMTOKEN", null); + attribute("th", "align", "NMTOKEN", null); + attribute("th", "colspan", "CDATA", "1"); + attribute("th", "headers", "IDREFS", null); + attribute("th", "nowrap", "BOOLEAN", null); + attribute("th", "rowspan", "CDATA", "1"); + attribute("th", "scope", "NMTOKEN", null); + attribute("th", "valign", "NMTOKEN", null); + attribute("thead", "align", "NMTOKEN", null); + attribute("thead", "valign", "NMTOKEN", null); + attribute("tr", "align", "NMTOKEN", null); + attribute("tr", "valign", "NMTOKEN", null); + attribute("ul", "compact", "BOOLEAN", null); + attribute("ul", "type", "NMTOKEN", null); + attribute("xmp", "width", "NMTOKEN", null); + attribute("a", "class", "NMTOKEN", null); + attribute("abbr", "class", "NMTOKEN", null); + attribute("acronym", "class", "NMTOKEN", null); + attribute("address", "class", "NMTOKEN", null); + attribute("applet", "class", "NMTOKEN", null); + attribute("area", "class", "NMTOKEN", null); + attribute("b", "class", "NMTOKEN", null); + attribute("base", "class", "NMTOKEN", null); + attribute("basefont", "class", "NMTOKEN", null); + attribute("bdo", "class", "NMTOKEN", null); + attribute("bgsound", "class", "NMTOKEN", null); + attribute("big", "class", "NMTOKEN", null); + attribute("blink", "class", "NMTOKEN", null); + attribute("blockquote", "class", "NMTOKEN", null); + attribute("body", "class", "NMTOKEN", null); + attribute("br", "class", "NMTOKEN", null); + attribute("button", "class", "NMTOKEN", null); + attribute("canvas", "class", "NMTOKEN", null); + attribute("caption", "class", "NMTOKEN", null); + attribute("center", "class", "NMTOKEN", null); + attribute("cite", "class", "NMTOKEN", null); + attribute("code", "class", "NMTOKEN", null); + attribute("col", "class", "NMTOKEN", null); + attribute("colgroup", "class", "NMTOKEN", null); + attribute("comment", "class", "NMTOKEN", null); + attribute("dd", "class", "NMTOKEN", null); + attribute("del", "class", "NMTOKEN", null); + attribute("dfn", "class", "NMTOKEN", null); + attribute("dir", "class", "NMTOKEN", null); + attribute("div", "class", "NMTOKEN", null); + attribute("dl", "class", "NMTOKEN", null); + attribute("dt", "class", "NMTOKEN", null); + attribute("em", "class", "NMTOKEN", null); + attribute("fieldset", "class", "NMTOKEN", null); + attribute("font", "class", "NMTOKEN", null); + attribute("form", "class", "NMTOKEN", null); + attribute("frame", "class", "NMTOKEN", null); + attribute("frameset", "class", "NMTOKEN", null); + attribute("h1", "class", "NMTOKEN", null); + attribute("h2", "class", "NMTOKEN", null); + attribute("h3", "class", "NMTOKEN", null); + attribute("h4", "class", "NMTOKEN", null); + attribute("h5", "class", "NMTOKEN", null); + attribute("h6", "class", "NMTOKEN", null); + attribute("head", "class", "NMTOKEN", null); + attribute("hr", "class", "NMTOKEN", null); + attribute("html", "class", "NMTOKEN", null); + attribute("i", "class", "NMTOKEN", null); + attribute("iframe", "class", "NMTOKEN", null); + attribute("img", "class", "NMTOKEN", null); + attribute("input", "class", "NMTOKEN", null); + attribute("ins", "class", "NMTOKEN", null); + attribute("isindex", "class", "NMTOKEN", null); + attribute("kbd", "class", "NMTOKEN", null); + attribute("label", "class", "NMTOKEN", null); + attribute("legend", "class", "NMTOKEN", null); + attribute("li", "class", "NMTOKEN", null); + attribute("link", "class", "NMTOKEN", null); + attribute("listing", "class", "NMTOKEN", null); + attribute("map", "class", "NMTOKEN", null); + attribute("marquee", "class", "NMTOKEN", null); + attribute("menu", "class", "NMTOKEN", null); + attribute("meta", "class", "NMTOKEN", null); + attribute("nobr", "class", "NMTOKEN", null); + attribute("noframes", "class", "NMTOKEN", null); + attribute("noscript", "class", "NMTOKEN", null); + attribute("object", "class", "NMTOKEN", null); + attribute("ol", "class", "NMTOKEN", null); + attribute("optgroup", "class", "NMTOKEN", null); + attribute("option", "class", "NMTOKEN", null); + attribute("p", "class", "NMTOKEN", null); + attribute("param", "class", "NMTOKEN", null); + attribute("pre", "class", "NMTOKEN", null); + attribute("q", "class", "NMTOKEN", null); + attribute("rb", "class", "NMTOKEN", null); + attribute("rbc", "class", "NMTOKEN", null); + attribute("rp", "class", "NMTOKEN", null); + attribute("rt", "class", "NMTOKEN", null); + attribute("rtc", "class", "NMTOKEN", null); + attribute("ruby", "class", "NMTOKEN", null); + attribute("s", "class", "NMTOKEN", null); + attribute("samp", "class", "NMTOKEN", null); + attribute("script", "class", "NMTOKEN", null); + attribute("select", "class", "NMTOKEN", null); + attribute("small", "class", "NMTOKEN", null); + attribute("span", "class", "NMTOKEN", null); + attribute("strike", "class", "NMTOKEN", null); + attribute("strong", "class", "NMTOKEN", null); + attribute("style", "class", "NMTOKEN", null); + attribute("sub", "class", "NMTOKEN", null); + attribute("sup", "class", "NMTOKEN", null); + attribute("table", "class", "NMTOKEN", null); + attribute("tbody", "class", "NMTOKEN", null); + attribute("td", "class", "NMTOKEN", null); + attribute("textarea", "class", "NMTOKEN", null); + attribute("tfoot", "class", "NMTOKEN", null); + attribute("th", "class", "NMTOKEN", null); + attribute("thead", "class", "NMTOKEN", null); + attribute("title", "class", "NMTOKEN", null); + attribute("tr", "class", "NMTOKEN", null); + attribute("tt", "class", "NMTOKEN", null); + attribute("u", "class", "NMTOKEN", null); + attribute("ul", "class", "NMTOKEN", null); + attribute("var", "class", "NMTOKEN", null); + attribute("wbr", "class", "NMTOKEN", null); + attribute("xmp", "class", "NMTOKEN", null); + attribute("a", "dir", "NMTOKEN", null); + attribute("abbr", "dir", "NMTOKEN", null); + attribute("acronym", "dir", "NMTOKEN", null); + attribute("address", "dir", "NMTOKEN", null); + attribute("applet", "dir", "NMTOKEN", null); + attribute("area", "dir", "NMTOKEN", null); + attribute("b", "dir", "NMTOKEN", null); + attribute("base", "dir", "NMTOKEN", null); + attribute("basefont", "dir", "NMTOKEN", null); + attribute("bdo", "dir", "NMTOKEN", null); + attribute("bgsound", "dir", "NMTOKEN", null); + attribute("big", "dir", "NMTOKEN", null); + attribute("blink", "dir", "NMTOKEN", null); + attribute("blockquote", "dir", "NMTOKEN", null); + attribute("body", "dir", "NMTOKEN", null); + attribute("br", "dir", "NMTOKEN", null); + attribute("button", "dir", "NMTOKEN", null); + attribute("canvas", "dir", "NMTOKEN", null); + attribute("caption", "dir", "NMTOKEN", null); + attribute("center", "dir", "NMTOKEN", null); + attribute("cite", "dir", "NMTOKEN", null); + attribute("code", "dir", "NMTOKEN", null); + attribute("col", "dir", "NMTOKEN", null); + attribute("colgroup", "dir", "NMTOKEN", null); + attribute("comment", "dir", "NMTOKEN", null); + attribute("dd", "dir", "NMTOKEN", null); + attribute("del", "dir", "NMTOKEN", null); + attribute("dfn", "dir", "NMTOKEN", null); + attribute("dir", "dir", "NMTOKEN", null); + attribute("div", "dir", "NMTOKEN", null); + attribute("dl", "dir", "NMTOKEN", null); + attribute("dt", "dir", "NMTOKEN", null); + attribute("em", "dir", "NMTOKEN", null); + attribute("fieldset", "dir", "NMTOKEN", null); + attribute("font", "dir", "NMTOKEN", null); + attribute("form", "dir", "NMTOKEN", null); + attribute("frame", "dir", "NMTOKEN", null); + attribute("frameset", "dir", "NMTOKEN", null); + attribute("h1", "dir", "NMTOKEN", null); + attribute("h2", "dir", "NMTOKEN", null); + attribute("h3", "dir", "NMTOKEN", null); + attribute("h4", "dir", "NMTOKEN", null); + attribute("h5", "dir", "NMTOKEN", null); + attribute("h6", "dir", "NMTOKEN", null); + attribute("head", "dir", "NMTOKEN", null); + attribute("hr", "dir", "NMTOKEN", null); + attribute("html", "dir", "NMTOKEN", null); + attribute("i", "dir", "NMTOKEN", null); + attribute("iframe", "dir", "NMTOKEN", null); + attribute("img", "dir", "NMTOKEN", null); + attribute("input", "dir", "NMTOKEN", null); + attribute("ins", "dir", "NMTOKEN", null); + attribute("isindex", "dir", "NMTOKEN", null); + attribute("kbd", "dir", "NMTOKEN", null); + attribute("label", "dir", "NMTOKEN", null); + attribute("legend", "dir", "NMTOKEN", null); + attribute("li", "dir", "NMTOKEN", null); + attribute("link", "dir", "NMTOKEN", null); + attribute("listing", "dir", "NMTOKEN", null); + attribute("map", "dir", "NMTOKEN", null); + attribute("marquee", "dir", "NMTOKEN", null); + attribute("menu", "dir", "NMTOKEN", null); + attribute("meta", "dir", "NMTOKEN", null); + attribute("nobr", "dir", "NMTOKEN", null); + attribute("noframes", "dir", "NMTOKEN", null); + attribute("noscript", "dir", "NMTOKEN", null); + attribute("object", "dir", "NMTOKEN", null); + attribute("ol", "dir", "NMTOKEN", null); + attribute("optgroup", "dir", "NMTOKEN", null); + attribute("option", "dir", "NMTOKEN", null); + attribute("p", "dir", "NMTOKEN", null); + attribute("param", "dir", "NMTOKEN", null); + attribute("pre", "dir", "NMTOKEN", null); + attribute("q", "dir", "NMTOKEN", null); + attribute("rb", "dir", "NMTOKEN", null); + attribute("rbc", "dir", "NMTOKEN", null); + attribute("rp", "dir", "NMTOKEN", null); + attribute("rt", "dir", "NMTOKEN", null); + attribute("rtc", "dir", "NMTOKEN", null); + attribute("ruby", "dir", "NMTOKEN", null); + attribute("s", "dir", "NMTOKEN", null); + attribute("samp", "dir", "NMTOKEN", null); + attribute("script", "dir", "NMTOKEN", null); + attribute("select", "dir", "NMTOKEN", null); + attribute("small", "dir", "NMTOKEN", null); + attribute("span", "dir", "NMTOKEN", null); + attribute("strike", "dir", "NMTOKEN", null); + attribute("strong", "dir", "NMTOKEN", null); + attribute("style", "dir", "NMTOKEN", null); + attribute("sub", "dir", "NMTOKEN", null); + attribute("sup", "dir", "NMTOKEN", null); + attribute("table", "dir", "NMTOKEN", null); + attribute("tbody", "dir", "NMTOKEN", null); + attribute("td", "dir", "NMTOKEN", null); + attribute("textarea", "dir", "NMTOKEN", null); + attribute("tfoot", "dir", "NMTOKEN", null); + attribute("th", "dir", "NMTOKEN", null); + attribute("thead", "dir", "NMTOKEN", null); + attribute("title", "dir", "NMTOKEN", null); + attribute("tr", "dir", "NMTOKEN", null); + attribute("tt", "dir", "NMTOKEN", null); + attribute("u", "dir", "NMTOKEN", null); + attribute("ul", "dir", "NMTOKEN", null); + attribute("var", "dir", "NMTOKEN", null); + attribute("wbr", "dir", "NMTOKEN", null); + attribute("xmp", "dir", "NMTOKEN", null); + attribute("a", "id", "ID", null); + attribute("abbr", "id", "ID", null); + attribute("acronym", "id", "ID", null); + attribute("address", "id", "ID", null); + attribute("applet", "id", "ID", null); + attribute("area", "id", "ID", null); + attribute("b", "id", "ID", null); + attribute("base", "id", "ID", null); + attribute("basefont", "id", "ID", null); + attribute("bdo", "id", "ID", null); + attribute("bgsound", "id", "ID", null); + attribute("big", "id", "ID", null); + attribute("blink", "id", "ID", null); + attribute("blockquote", "id", "ID", null); + attribute("body", "id", "ID", null); + attribute("br", "id", "ID", null); + attribute("button", "id", "ID", null); + attribute("canvas", "id", "ID", null); + attribute("caption", "id", "ID", null); + attribute("center", "id", "ID", null); + attribute("cite", "id", "ID", null); + attribute("code", "id", "ID", null); + attribute("col", "id", "ID", null); + attribute("colgroup", "id", "ID", null); + attribute("comment", "id", "ID", null); + attribute("dd", "id", "ID", null); + attribute("del", "id", "ID", null); + attribute("dfn", "id", "ID", null); + attribute("dir", "id", "ID", null); + attribute("div", "id", "ID", null); + attribute("dl", "id", "ID", null); + attribute("dt", "id", "ID", null); + attribute("em", "id", "ID", null); + attribute("fieldset", "id", "ID", null); + attribute("font", "id", "ID", null); + attribute("form", "id", "ID", null); + attribute("frame", "id", "ID", null); + attribute("frameset", "id", "ID", null); + attribute("h1", "id", "ID", null); + attribute("h2", "id", "ID", null); + attribute("h3", "id", "ID", null); + attribute("h4", "id", "ID", null); + attribute("h5", "id", "ID", null); + attribute("h6", "id", "ID", null); + attribute("head", "id", "ID", null); + attribute("hr", "id", "ID", null); + attribute("html", "id", "ID", null); + attribute("i", "id", "ID", null); + attribute("iframe", "id", "ID", null); + attribute("img", "id", "ID", null); + attribute("input", "id", "ID", null); + attribute("ins", "id", "ID", null); + attribute("isindex", "id", "ID", null); + attribute("kbd", "id", "ID", null); + attribute("label", "id", "ID", null); + attribute("legend", "id", "ID", null); + attribute("li", "id", "ID", null); + attribute("link", "id", "ID", null); + attribute("listing", "id", "ID", null); + attribute("map", "id", "ID", null); + attribute("marquee", "id", "ID", null); + attribute("menu", "id", "ID", null); + attribute("meta", "id", "ID", null); + attribute("nobr", "id", "ID", null); + attribute("noframes", "id", "ID", null); + attribute("noscript", "id", "ID", null); + attribute("object", "id", "ID", null); + attribute("ol", "id", "ID", null); + attribute("optgroup", "id", "ID", null); + attribute("option", "id", "ID", null); + attribute("p", "id", "ID", null); + attribute("param", "id", "ID", null); + attribute("pre", "id", "ID", null); + attribute("q", "id", "ID", null); + attribute("rb", "id", "ID", null); + attribute("rbc", "id", "ID", null); + attribute("rp", "id", "ID", null); + attribute("rt", "id", "ID", null); + attribute("rtc", "id", "ID", null); + attribute("ruby", "id", "ID", null); + attribute("s", "id", "ID", null); + attribute("samp", "id", "ID", null); + attribute("script", "id", "ID", null); + attribute("select", "id", "ID", null); + attribute("small", "id", "ID", null); + attribute("span", "id", "ID", null); + attribute("strike", "id", "ID", null); + attribute("strong", "id", "ID", null); + attribute("style", "id", "ID", null); + attribute("sub", "id", "ID", null); + attribute("sup", "id", "ID", null); + attribute("table", "id", "ID", null); + attribute("tbody", "id", "ID", null); + attribute("td", "id", "ID", null); + attribute("textarea", "id", "ID", null); + attribute("tfoot", "id", "ID", null); + attribute("th", "id", "ID", null); + attribute("thead", "id", "ID", null); + attribute("title", "id", "ID", null); + attribute("tr", "id", "ID", null); + attribute("tt", "id", "ID", null); + attribute("u", "id", "ID", null); + attribute("ul", "id", "ID", null); + attribute("var", "id", "ID", null); + attribute("wbr", "id", "ID", null); + attribute("xmp", "id", "ID", null); + attribute("a", "lang", "NMTOKEN", null); + attribute("abbr", "lang", "NMTOKEN", null); + attribute("acronym", "lang", "NMTOKEN", null); + attribute("address", "lang", "NMTOKEN", null); + attribute("applet", "lang", "NMTOKEN", null); + attribute("area", "lang", "NMTOKEN", null); + attribute("b", "lang", "NMTOKEN", null); + attribute("base", "lang", "NMTOKEN", null); + attribute("basefont", "lang", "NMTOKEN", null); + attribute("bdo", "lang", "NMTOKEN", null); + attribute("bgsound", "lang", "NMTOKEN", null); + attribute("big", "lang", "NMTOKEN", null); + attribute("blink", "lang", "NMTOKEN", null); + attribute("blockquote", "lang", "NMTOKEN", null); + attribute("body", "lang", "NMTOKEN", null); + attribute("br", "lang", "NMTOKEN", null); + attribute("button", "lang", "NMTOKEN", null); + attribute("canvas", "lang", "NMTOKEN", null); + attribute("caption", "lang", "NMTOKEN", null); + attribute("center", "lang", "NMTOKEN", null); + attribute("cite", "lang", "NMTOKEN", null); + attribute("code", "lang", "NMTOKEN", null); + attribute("col", "lang", "NMTOKEN", null); + attribute("colgroup", "lang", "NMTOKEN", null); + attribute("comment", "lang", "NMTOKEN", null); + attribute("dd", "lang", "NMTOKEN", null); + attribute("del", "lang", "NMTOKEN", null); + attribute("dfn", "lang", "NMTOKEN", null); + attribute("dir", "lang", "NMTOKEN", null); + attribute("div", "lang", "NMTOKEN", null); + attribute("dl", "lang", "NMTOKEN", null); + attribute("dt", "lang", "NMTOKEN", null); + attribute("em", "lang", "NMTOKEN", null); + attribute("fieldset", "lang", "NMTOKEN", null); + attribute("font", "lang", "NMTOKEN", null); + attribute("form", "lang", "NMTOKEN", null); + attribute("frame", "lang", "NMTOKEN", null); + attribute("frameset", "lang", "NMTOKEN", null); + attribute("h1", "lang", "NMTOKEN", null); + attribute("h2", "lang", "NMTOKEN", null); + attribute("h3", "lang", "NMTOKEN", null); + attribute("h4", "lang", "NMTOKEN", null); + attribute("h5", "lang", "NMTOKEN", null); + attribute("h6", "lang", "NMTOKEN", null); + attribute("head", "lang", "NMTOKEN", null); + attribute("hr", "lang", "NMTOKEN", null); + attribute("html", "lang", "NMTOKEN", null); + attribute("i", "lang", "NMTOKEN", null); + attribute("iframe", "lang", "NMTOKEN", null); + attribute("img", "lang", "NMTOKEN", null); + attribute("input", "lang", "NMTOKEN", null); + attribute("ins", "lang", "NMTOKEN", null); + attribute("isindex", "lang", "NMTOKEN", null); + attribute("kbd", "lang", "NMTOKEN", null); + attribute("label", "lang", "NMTOKEN", null); + attribute("legend", "lang", "NMTOKEN", null); + attribute("li", "lang", "NMTOKEN", null); + attribute("link", "lang", "NMTOKEN", null); + attribute("listing", "lang", "NMTOKEN", null); + attribute("map", "lang", "NMTOKEN", null); + attribute("marquee", "lang", "NMTOKEN", null); + attribute("menu", "lang", "NMTOKEN", null); + attribute("meta", "lang", "NMTOKEN", null); + attribute("nobr", "lang", "NMTOKEN", null); + attribute("noframes", "lang", "NMTOKEN", null); + attribute("noscript", "lang", "NMTOKEN", null); + attribute("object", "lang", "NMTOKEN", null); + attribute("ol", "lang", "NMTOKEN", null); + attribute("optgroup", "lang", "NMTOKEN", null); + attribute("option", "lang", "NMTOKEN", null); + attribute("p", "lang", "NMTOKEN", null); + attribute("param", "lang", "NMTOKEN", null); + attribute("pre", "lang", "NMTOKEN", null); + attribute("q", "lang", "NMTOKEN", null); + attribute("rb", "lang", "NMTOKEN", null); + attribute("rbc", "lang", "NMTOKEN", null); + attribute("rp", "lang", "NMTOKEN", null); + attribute("rt", "lang", "NMTOKEN", null); + attribute("rtc", "lang", "NMTOKEN", null); + attribute("ruby", "lang", "NMTOKEN", null); + attribute("s", "lang", "NMTOKEN", null); + attribute("samp", "lang", "NMTOKEN", null); + attribute("script", "lang", "NMTOKEN", null); + attribute("select", "lang", "NMTOKEN", null); + attribute("small", "lang", "NMTOKEN", null); + attribute("span", "lang", "NMTOKEN", null); + attribute("strike", "lang", "NMTOKEN", null); + attribute("strong", "lang", "NMTOKEN", null); + attribute("style", "lang", "NMTOKEN", null); + attribute("sub", "lang", "NMTOKEN", null); + attribute("sup", "lang", "NMTOKEN", null); + attribute("table", "lang", "NMTOKEN", null); + attribute("tbody", "lang", "NMTOKEN", null); + attribute("td", "lang", "NMTOKEN", null); + attribute("textarea", "lang", "NMTOKEN", null); + attribute("tfoot", "lang", "NMTOKEN", null); + attribute("th", "lang", "NMTOKEN", null); + attribute("thead", "lang", "NMTOKEN", null); + attribute("title", "lang", "NMTOKEN", null); + attribute("tr", "lang", "NMTOKEN", null); + attribute("tt", "lang", "NMTOKEN", null); + attribute("u", "lang", "NMTOKEN", null); + attribute("ul", "lang", "NMTOKEN", null); + attribute("var", "lang", "NMTOKEN", null); + attribute("wbr", "lang", "NMTOKEN", null); + attribute("xmp", "lang", "NMTOKEN", null); + entity("Aacgr", 0x0386); + entity("aacgr", 0x03AC); + entity("Aacute", 0x00C1); + entity("aacute", 0x00E1); + entity("Abreve", 0x0102); + entity("abreve", 0x0103); + entity("ac", 0x223E); + entity("acd", 0x223F); + entity("Acirc", 0x00C2); + entity("acirc", 0x00E2); + entity("acute", 0x00B4); + entity("Acy", 0x0410); + entity("acy", 0x0430); + entity("AElig", 0x00C6); + entity("aelig", 0x00E6); + entity("af", 0x2061); + entity("Afr", 0x1D504); + entity("afr", 0x1D51E); + entity("Agr", 0x0391); + entity("agr", 0x03B1); + entity("Agrave", 0x00C0); + entity("agrave", 0x00E0); + entity("alefsym", 0x2135); + entity("aleph", 0x2135); + entity("Alpha", 0x0391); + entity("alpha", 0x03B1); + entity("Amacr", 0x0100); + entity("amacr", 0x0101); + entity("amalg", 0x2A3F); + entity("amp", 0x0026); + entity("And", 0x2A53); + entity("and", 0x2227); + entity("andand", 0x2A55); + entity("andd", 0x2A5C); + entity("andslope", 0x2A58); + entity("andv", 0x2A5A); + entity("ang", 0x2220); + entity("ange", 0x29A4); + entity("angle", 0x2220); + entity("angmsd", 0x2221); + entity("angmsdaa", 0x29A8); + entity("angmsdab", 0x29A9); + entity("angmsdac", 0x29AA); + entity("angmsdad", 0x29AB); + entity("angmsdae", 0x29AC); + entity("angmsdaf", 0x29AD); + entity("angmsdag", 0x29AE); + entity("angmsdah", 0x29AF); + entity("angrt", 0x221F); + entity("angrtvb", 0x22BE); + entity("angrtvbd", 0x299D); + entity("angsph", 0x2222); + entity("angst", 0x212B); + entity("angzarr", 0x237C); + entity("Aogon", 0x0104); + entity("aogon", 0x0105); + entity("Aopf", 0x1D538); + entity("aopf", 0x1D552); + entity("ap", 0x2248); + entity("apacir", 0x2A6F); + entity("apE", 0x2A70); + entity("ape", 0x224A); + entity("apid", 0x224B); + entity("apos", 0x0027); + entity("ApplyFunction", 0x2061); + entity("approx", 0x2248); + entity("approxeq", 0x224A); + entity("Aring", 0x00C5); + entity("aring", 0x00E5); + entity("Ascr", 0x1D49C); + entity("ascr", 0x1D4B6); + entity("Assign", 0x2254); + entity("ast", 0x002A); + entity("asymp", 0x2248); + entity("asympeq", 0x224D); + entity("Atilde", 0x00C3); + entity("atilde", 0x00E3); + entity("Auml", 0x00C4); + entity("auml", 0x00E4); + entity("awconint", 0x2233); + entity("awint", 0x2A11); + entity("b.alpha", 0x1D6C2); + entity("b.beta", 0x1D6C3); + entity("b.chi", 0x1D6D8); + entity("b.Delta", 0x1D6AB); + entity("b.delta", 0x1D6C5); + entity("b.epsi", 0x1D6C6); + entity("b.epsiv", 0x1D6DC); + entity("b.eta", 0x1D6C8); + entity("b.Gamma", 0x1D6AA); + entity("b.gamma", 0x1D6C4); + entity("b.Gammad", 0x1D7CA); + entity("b.gammad", 0x1D7CB); + entity("b.iota", 0x1D6CA); + entity("b.kappa", 0x1D6CB); + entity("b.kappav", 0x1D6DE); + entity("b.Lambda", 0x1D6B2); + entity("b.lambda", 0x1D6CC); + entity("b.mu", 0x1D6CD); + entity("b.nu", 0x1D6CE); + entity("b.Omega", 0x1D6C0); + entity("b.omega", 0x1D6DA); + entity("b.Phi", 0x1D6BD); + entity("b.phi", 0x1D6D7); + entity("b.phiv", 0x1D6DF); + entity("b.Pi", 0x1D6B7); + entity("b.pi", 0x1D6D1); + entity("b.piv", 0x1D6E1); + entity("b.Psi", 0x1D6BF); + entity("b.psi", 0x1D6D9); + entity("b.rho", 0x1D6D2); + entity("b.rhov", 0x1D6E0); + entity("b.Sigma", 0x1D6BA); + entity("b.sigma", 0x1D6D4); + entity("b.sigmav", 0x1D6D3); + entity("b.tau", 0x1D6D5); + entity("b.Theta", 0x1D6AF); + entity("b.thetas", 0x1D6C9); + entity("b.thetav", 0x1D6DD); + entity("b.Upsi", 0x1D6BC); + entity("b.upsi", 0x1D6D6); + entity("b.Xi", 0x1D6B5); + entity("b.xi", 0x1D6CF); + entity("b.zeta", 0x1D6C7); + entity("backcong", 0x224C); + entity("backepsilon", 0x03F6); + entity("backprime", 0x2035); + entity("backsim", 0x223D); + entity("backsimeq", 0x22CD); + entity("Backslash", 0x2216); + entity("Barv", 0x2AE7); + entity("barvee", 0x22BD); + entity("Barwed", 0x2306); + entity("barwed", 0x2305); + entity("barwedge", 0x2305); + entity("bbrk", 0x23B5); + entity("bbrktbrk", 0x23B6); + entity("bcong", 0x224C); + entity("Bcy", 0x0411); + entity("bcy", 0x0431); + entity("bdquo", 0x201E); + entity("becaus", 0x2235); + entity("because", 0x2235); + entity("bemptyv", 0x29B0); + entity("bepsi", 0x03F6); + entity("bernou", 0x212C); + entity("Bernoullis", 0x212C); + entity("Beta", 0x0392); + entity("beta", 0x03B2); + entity("beth", 0x2136); + entity("between", 0x226C); + entity("Bfr", 0x1D505); + entity("bfr", 0x1D51F); + entity("Bgr", 0x0392); + entity("bgr", 0x03B2); + entity("bigcap", 0x22C2); + entity("bigcirc", 0x25EF); + entity("bigcup", 0x22C3); + entity("bigodot", 0x2A00); + entity("bigoplus", 0x2A01); + entity("bigotimes", 0x2A02); + entity("bigsqcup", 0x2A06); + entity("bigstar", 0x2605); + entity("bigtriangledown", 0x25BD); + entity("bigtriangleup", 0x25B3); + entity("biguplus", 0x2A04); + entity("bigvee", 0x22C1); + entity("bigwedge", 0x22C0); + entity("bkarow", 0x290D); + entity("blacklozenge", 0x29EB); + entity("blacksquare", 0x25AA); + entity("blacktriangle", 0x25B4); + entity("blacktriangledown", 0x25BE); + entity("blacktriangleleft", 0x25C2); + entity("blacktriangleright", 0x25B8); + entity("blank", 0x2423); + entity("blk12", 0x2592); + entity("blk14", 0x2591); + entity("blk34", 0x2593); + entity("block", 0x2588); + entity("bNot", 0x2AED); + entity("bnot", 0x2310); + entity("Bopf", 0x1D539); + entity("bopf", 0x1D553); + entity("bot", 0x22A5); + entity("bottom", 0x22A5); + entity("bowtie", 0x22C8); + entity("boxbox", 0x29C9); + entity("boxDL", 0x2557); + entity("boxDl", 0x2556); + entity("boxdL", 0x2555); + entity("boxdl", 0x2510); + entity("boxDR", 0x2554); + entity("boxDr", 0x2553); + entity("boxdR", 0x2552); + entity("boxdr", 0x250C); + entity("boxH", 0x2550); + entity("boxh", 0x2500); + entity("boxHD", 0x2566); + entity("boxHd", 0x2564); + entity("boxhD", 0x2565); + entity("boxhd", 0x252C); + entity("boxHU", 0x2569); + entity("boxHu", 0x2567); + entity("boxhU", 0x2568); + entity("boxhu", 0x2534); + entity("boxminus", 0x229F); + entity("boxplus", 0x229E); + entity("boxtimes", 0x22A0); + entity("boxUL", 0x255D); + entity("boxUl", 0x255C); + entity("boxuL", 0x255B); + entity("boxul", 0x2518); + entity("boxUR", 0x255A); + entity("boxUr", 0x2559); + entity("boxuR", 0x2558); + entity("boxur", 0x2514); + entity("boxV", 0x2551); + entity("boxv", 0x2502); + entity("boxVH", 0x256C); + entity("boxVh", 0x256B); + entity("boxvH", 0x256A); + entity("boxvh", 0x253C); + entity("boxVL", 0x2563); + entity("boxVl", 0x2562); + entity("boxvL", 0x2561); + entity("boxvl", 0x2524); + entity("boxVR", 0x2560); + entity("boxVr", 0x255F); + entity("boxvR", 0x255E); + entity("boxvr", 0x251C); + entity("bprime", 0x2035); + entity("breve", 0x02D8); + entity("brvbar", 0x00A6); + entity("Bscr", 0x212C); + entity("bscr", 0x1D4B7); + entity("bsemi", 0x204F); + entity("bsim", 0x223D); + entity("bsime", 0x22CD); + entity("bsol", 0x005C); + entity("bsolb", 0x29C5); + entity("bull", 0x2022); + entity("bullet", 0x2022); + entity("bump", 0x224E); + entity("bumpE", 0x2AAE); + entity("bumpe", 0x224F); + entity("Bumpeq", 0x224E); + entity("bumpeq", 0x224F); + entity("Cacute", 0x0106); + entity("cacute", 0x0107); + entity("Cap", 0x22D2); + entity("cap", 0x2229); + entity("capand", 0x2A44); + entity("capbrcup", 0x2A49); + entity("capcap", 0x2A4B); + entity("capcup", 0x2A47); + entity("capdot", 0x2A40); + entity("CapitalDifferentialD", 0x2145); + entity("caret", 0x2041); + entity("caron", 0x02C7); + entity("Cayleys", 0x212D); + entity("ccaps", 0x2A4D); + entity("Ccaron", 0x010C); + entity("ccaron", 0x010D); + entity("Ccedil", 0x00C7); + entity("ccedil", 0x00E7); + entity("Ccirc", 0x0108); + entity("ccirc", 0x0109); + entity("Cconint", 0x2230); + entity("ccups", 0x2A4C); + entity("ccupssm", 0x2A50); + entity("Cdot", 0x010A); + entity("cdot", 0x010B); + entity("cedil", 0x00B8); + entity("Cedilla", 0x00B8); + entity("cemptyv", 0x29B2); + entity("cent", 0x00A2); + entity("centerdot", 0x00B7); + entity("Cfr", 0x212D); + entity("cfr", 0x1D520); + entity("CHcy", 0x0427); + entity("chcy", 0x0447); + entity("check", 0x2713); + entity("checkmark", 0x2713); + entity("Chi", 0x03A7); + entity("chi", 0x03C7); + entity("cir", 0x25CB); + entity("circ", 0x02C6); + entity("circeq", 0x2257); + entity("circlearrowleft", 0x21BA); + entity("circlearrowright", 0x21BB); + entity("circledast", 0x229B); + entity("circledcirc", 0x229A); + entity("circleddash", 0x229D); + entity("CircleDot", 0x2299); + entity("circledR", 0x00AE); + entity("circledS", 0x24C8); + entity("CircleMinus", 0x2296); + entity("CirclePlus", 0x2295); + entity("CircleTimes", 0x2297); + entity("cirE", 0x29C3); + entity("cire", 0x2257); + entity("cirfnint", 0x2A10); + entity("cirmid", 0x2AEF); + entity("cirscir", 0x29C2); + entity("ClockwiseContourIntegral", 0x2232); + entity("CloseCurlyDoubleQuote", 0x201D); + entity("CloseCurlyQuote", 0x2019); + entity("clubs", 0x2663); + entity("clubsuit", 0x2663); + entity("Colon", 0x2237); + entity("colon", 0x003A); + entity("Colone", 0x2A74); + entity("colone", 0x2254); + entity("coloneq", 0x2254); + entity("comma", 0x002C); + entity("commat", 0x0040); + entity("comp", 0x2201); + entity("compfn", 0x2218); + entity("complement", 0x2201); + entity("complexes", 0x2102); + entity("cong", 0x2245); + entity("congdot", 0x2A6D); + entity("Congruent", 0x2261); + entity("Conint", 0x222F); + entity("conint", 0x222E); + entity("ContourIntegral", 0x222E); + entity("Copf", 0x2102); + entity("copf", 0x1D554); + entity("coprod", 0x2210); + entity("Coproduct", 0x2210); + entity("copy", 0x00A9); + entity("copysr", 0x2117); + entity("CounterClockwiseContourIntegral", 0x2233); + entity("crarr", 0x21B5); + entity("Cross", 0x2A2F); + entity("cross", 0x2717); + entity("Cscr", 0x1D49E); + entity("cscr", 0x1D4B8); + entity("csub", 0x2ACF); + entity("csube", 0x2AD1); + entity("csup", 0x2AD0); + entity("csupe", 0x2AD2); + entity("ctdot", 0x22EF); + entity("cudarrl", 0x2938); + entity("cudarrr", 0x2935); + entity("cuepr", 0x22DE); + entity("cuesc", 0x22DF); + entity("cularr", 0x21B6); + entity("cularrp", 0x293D); + entity("Cup", 0x22D3); + entity("cup", 0x222A); + entity("cupbrcap", 0x2A48); + entity("CupCap", 0x224D); + entity("cupcap", 0x2A46); + entity("cupcup", 0x2A4A); + entity("cupdot", 0x228D); + entity("cupor", 0x2A45); + entity("curarr", 0x21B7); + entity("curarrm", 0x293C); + entity("curlyeqprec", 0x22DE); + entity("curlyeqsucc", 0x22DF); + entity("curlyvee", 0x22CE); + entity("curlywedge", 0x22CF); + entity("curren", 0x00A4); + entity("curvearrowleft", 0x21B6); + entity("curvearrowright", 0x21B7); + entity("cuvee", 0x22CE); + entity("cuwed", 0x22CF); + entity("cwconint", 0x2232); + entity("cwint", 0x2231); + entity("cylcty", 0x232D); + entity("Dagger", 0x2021); + entity("dagger", 0x2020); + entity("daleth", 0x2138); + entity("Darr", 0x21A1); + entity("dArr", 0x21D3); + entity("darr", 0x2193); + entity("dash", 0x2010); + entity("Dashv", 0x2AE4); + entity("dashv", 0x22A3); + entity("dbkarow", 0x290F); + entity("dblac", 0x02DD); + entity("Dcaron", 0x010E); + entity("dcaron", 0x010F); + entity("Dcy", 0x0414); + entity("dcy", 0x0434); + entity("DD", 0x2145); + entity("dd", 0x2146); + entity("ddagger", 0x2021); + entity("ddarr", 0x21CA); + entity("DDotrahd", 0x2911); + entity("ddotseq", 0x2A77); + entity("deg", 0x00B0); + entity("Del", 0x2207); + entity("Delta", 0x0394); + entity("delta", 0x03B4); + entity("demptyv", 0x29B1); + entity("dfisht", 0x297F); + entity("Dfr", 0x1D507); + entity("dfr", 0x1D521); + entity("Dgr", 0x0394); + entity("dgr", 0x03B4); + entity("dHar", 0x2965); + entity("dharl", 0x21C3); + entity("dharr", 0x21C2); + entity("DiacriticalAcute", 0x00B4); + entity("DiacriticalDot", 0x02D9); + entity("DiacriticalDoubleAcute", 0x02DD); + entity("DiacriticalGrave", 0x0060); + entity("DiacriticalTilde", 0x02DC); + entity("diam", 0x22C4); + entity("diamond", 0x22C4); + entity("diamondsuit", 0x2666); + entity("diams", 0x2666); + entity("die", 0x00A8); + entity("DifferentialD", 0x2146); + entity("digamma", 0x03DD); + entity("disin", 0x22F2); + entity("div", 0x00F7); + entity("divide", 0x00F7); + entity("divideontimes", 0x22C7); + entity("divonx", 0x22C7); + entity("DJcy", 0x0402); + entity("djcy", 0x0452); + entity("dlcorn", 0x231E); + entity("dlcrop", 0x230D); + entity("dollar", 0x0024); + entity("Dopf", 0x1D53B); + entity("dopf", 0x1D555); + entity("Dot", 0x00A8); + entity("dot", 0x02D9); + entity("doteq", 0x2250); + entity("doteqdot", 0x2251); + entity("DotEqual", 0x2250); + entity("dotminus", 0x2238); + entity("dotplus", 0x2214); + entity("dotsquare", 0x22A1); + entity("doublebarwedge", 0x2306); + entity("DoubleContourIntegral", 0x222F); + entity("DoubleDot", 0x00A8); + entity("DoubleDownArrow", 0x21D3); + entity("DoubleLeftArrow", 0x21D0); + entity("DoubleLeftRightArrow", 0x21D4); + entity("DoubleLeftTee", 0x2AE4); + entity("DoubleLongLeftArrow", 0x27F8); + entity("DoubleLongLeftRightArrow", 0x27FA); + entity("DoubleLongRightArrow", 0x27F9); + entity("DoubleRightArrow", 0x21D2); + entity("DoubleRightTee", 0x22A8); + entity("DoubleUpArrow", 0x21D1); + entity("DoubleUpDownArrow", 0x21D5); + entity("DoubleVerticalBar", 0x2225); + entity("Downarrow", 0x21D3); + entity("downarrow", 0x2193); + entity("DownArrowBar", 0x2913); + entity("DownArrowUpArrow", 0x21F5); + entity("downdownarrows", 0x21CA); + entity("downharpoonleft", 0x21C3); + entity("downharpoonright", 0x21C2); + entity("DownLeftRightVector", 0x2950); + entity("DownLeftTeeVector", 0x295E); + entity("DownLeftVector", 0x21BD); + entity("DownLeftVectorBar", 0x2956); + entity("DownRightTeeVector", 0x295F); + entity("DownRightVector", 0x21C1); + entity("DownRightVectorBar", 0x2957); + entity("DownTee", 0x22A4); + entity("DownTeeArrow", 0x21A7); + entity("drbkarow", 0x2910); + entity("drcorn", 0x231F); + entity("drcrop", 0x230C); + entity("Dscr", 0x1D49F); + entity("dscr", 0x1D4B9); + entity("DScy", 0x0405); + entity("dscy", 0x0455); + entity("dsol", 0x29F6); + entity("Dstrok", 0x0110); + entity("dstrok", 0x0111); + entity("dtdot", 0x22F1); + entity("dtri", 0x25BF); + entity("dtrif", 0x25BE); + entity("duarr", 0x21F5); + entity("duhar", 0x296F); + entity("dwangle", 0x29A6); + entity("DZcy", 0x040F); + entity("dzcy", 0x045F); + entity("dzigrarr", 0x27FF); + entity("Eacgr", 0x0388); + entity("eacgr", 0x03AD); + entity("Eacute", 0x00C9); + entity("eacute", 0x00E9); + entity("easter", 0x2A6E); + entity("Ecaron", 0x011A); + entity("ecaron", 0x011B); + entity("ecir", 0x2256); + entity("Ecirc", 0x00CA); + entity("ecirc", 0x00EA); + entity("ecolon", 0x2255); + entity("Ecy", 0x042D); + entity("ecy", 0x044D); + entity("eDDot", 0x2A77); + entity("Edot", 0x0116); + entity("eDot", 0x2251); + entity("edot", 0x0117); + entity("ee", 0x2147); + entity("EEacgr", 0x0389); + entity("eeacgr", 0x03AE); + entity("EEgr", 0x0397); + entity("eegr", 0x03B7); + entity("efDot", 0x2252); + entity("Efr", 0x1D508); + entity("efr", 0x1D522); + entity("eg", 0x2A9A); + entity("Egr", 0x0395); + entity("egr", 0x03B5); + entity("Egrave", 0x00C8); + entity("egrave", 0x00E8); + entity("egs", 0x2A96); + entity("egsdot", 0x2A98); + entity("el", 0x2A99); + entity("Element", 0x2208); + entity("elinters", 0x23E7); + entity("ell", 0x2113); + entity("els", 0x2A95); + entity("elsdot", 0x2A97); + entity("Emacr", 0x0112); + entity("emacr", 0x0113); + entity("empty", 0x2205); + entity("emptyset", 0x2205); + entity("EmptySmallSquare", 0x25FB); + entity("emptyv", 0x2205); + entity("EmptyVerySmallSquare", 0x25AB); + entity("emsp", 0x2003); + entity("emsp13", 0x2004); + entity("emsp14", 0x2005); + entity("ENG", 0x014A); + entity("eng", 0x014B); + entity("ensp", 0x2002); + entity("Eogon", 0x0118); + entity("eogon", 0x0119); + entity("Eopf", 0x1D53C); + entity("eopf", 0x1D556); + entity("epar", 0x22D5); + entity("eparsl", 0x29E3); + entity("eplus", 0x2A71); + entity("epsi", 0x03F5); + entity("Epsilon", 0x0395); + entity("epsilon", 0x03B5); + entity("epsiv", 0x03B5); + entity("eqcirc", 0x2256); + entity("eqcolon", 0x2255); + entity("eqsim", 0x2242); + entity("eqslantgtr", 0x2A96); + entity("eqslantless", 0x2A95); + entity("Equal", 0x2A75); + entity("equals", 0x003D); + entity("EqualTilde", 0x2242); + entity("equest", 0x225F); + entity("Equilibrium", 0x21CC); + entity("equiv", 0x2261); + entity("equivDD", 0x2A78); + entity("eqvparsl", 0x29E5); + entity("erarr", 0x2971); + entity("erDot", 0x2253); + entity("Escr", 0x2130); + entity("escr", 0x212F); + entity("esdot", 0x2250); + entity("Esim", 0x2A73); + entity("esim", 0x2242); + entity("Eta", 0x0397); + entity("eta", 0x03B7); + entity("ETH", 0x00D0); + entity("eth", 0x00F0); + entity("Euml", 0x00CB); + entity("euml", 0x00EB); + entity("euro", 0x20AC); + entity("excl", 0x0021); + entity("exist", 0x2203); + entity("Exists", 0x2203); + entity("expectation", 0x2130); + entity("exponentiale", 0x2147); + entity("fallingdotseq", 0x2252); + entity("Fcy", 0x0424); + entity("fcy", 0x0444); + entity("female", 0x2640); + entity("ffilig", 0xFB03); + entity("fflig", 0xFB00); + entity("ffllig", 0xFB04); + entity("Ffr", 0x1D509); + entity("ffr", 0x1D523); + entity("filig", 0xFB01); + entity("FilledSmallSquare", 0x25FC); + entity("FilledVerySmallSquare", 0x25AA); + entity("flat", 0x266D); + entity("fllig", 0xFB02); + entity("fltns", 0x25B1); + entity("fnof", 0x0192); + entity("Fopf", 0x1D53D); + entity("fopf", 0x1D557); + entity("forall", 0x2200); + entity("fork", 0x22D4); + entity("forkv", 0x2AD9); + entity("Fouriertrf", 0x2131); + entity("fpartint", 0x2A0D); + entity("frac12", 0x00BD); + entity("frac13", 0x2153); + entity("frac14", 0x00BC); + entity("frac15", 0x2155); + entity("frac16", 0x2159); + entity("frac18", 0x215B); + entity("frac23", 0x2154); + entity("frac25", 0x2156); + entity("frac34", 0x00BE); + entity("frac35", 0x2157); + entity("frac38", 0x215C); + entity("frac45", 0x2158); + entity("frac56", 0x215A); + entity("frac58", 0x215D); + entity("frac78", 0x215E); + entity("frasl", 0x2044); + entity("frown", 0x2322); + entity("Fscr", 0x2131); + entity("fscr", 0x1D4BB); + entity("gacute", 0x01F5); + entity("Gamma", 0x0393); + entity("gamma", 0x03B3); + entity("Gammad", 0x03DC); + entity("gammad", 0x03DD); + entity("gap", 0x2A86); + entity("Gbreve", 0x011E); + entity("gbreve", 0x011F); + entity("Gcedil", 0x0122); + entity("Gcirc", 0x011C); + entity("gcirc", 0x011D); + entity("Gcy", 0x0413); + entity("gcy", 0x0433); + entity("Gdot", 0x0120); + entity("gdot", 0x0121); + entity("gE", 0x2267); + entity("ge", 0x2265); + entity("gEl", 0x2A8C); + entity("gel", 0x22DB); + entity("geq", 0x2265); + entity("geqq", 0x2267); + entity("geqslant", 0x2A7E); + entity("ges", 0x2A7E); + entity("gescc", 0x2AA9); + entity("gesdot", 0x2A80); + entity("gesdoto", 0x2A82); + entity("gesdotol", 0x2A84); + entity("gesles", 0x2A94); + entity("Gfr", 0x1D50A); + entity("gfr", 0x1D524); + entity("Gg", 0x22D9); + entity("gg", 0x226B); + entity("ggg", 0x22D9); + entity("Ggr", 0x0393); + entity("ggr", 0x03B3); + entity("gimel", 0x2137); + entity("GJcy", 0x0403); + entity("gjcy", 0x0453); + entity("gl", 0x2277); + entity("gla", 0x2AA5); + entity("glE", 0x2A92); + entity("glj", 0x2AA4); + entity("gnap", 0x2A8A); + entity("gnapprox", 0x2A8A); + entity("gnE", 0x2269); + entity("gne", 0x2A88); + entity("gneq", 0x2A88); + entity("gneqq", 0x2269); + entity("gnsim", 0x22E7); + entity("Gopf", 0x1D53E); + entity("gopf", 0x1D558); + entity("grave", 0x0060); + entity("GreaterEqual", 0x2265); + entity("GreaterEqualLess", 0x22DB); + entity("GreaterFullEqual", 0x2267); + entity("GreaterGreater", 0x2AA2); + entity("GreaterLess", 0x2277); + entity("GreaterSlantEqual", 0x2A7E); + entity("GreaterTilde", 0x2273); + entity("Gscr", 0x1D4A2); + entity("gscr", 0x210A); + entity("gsim", 0x2273); + entity("gsime", 0x2A8E); + entity("gsiml", 0x2A90); + entity("Gt", 0x226B); + entity("gt", 0x003E); + entity("gtcc", 0x2AA7); + entity("gtcir", 0x2A7A); + entity("gtdot", 0x22D7); + entity("gtlPar", 0x2995); + entity("gtquest", 0x2A7C); + entity("gtrapprox", 0x2A86); + entity("gtrarr", 0x2978); + entity("gtrdot", 0x22D7); + entity("gtreqless", 0x22DB); + entity("gtreqqless", 0x2A8C); + entity("gtrless", 0x2277); + entity("gtrsim", 0x2273); + entity("Hacek", 0x02C7); + entity("hairsp", 0x200A); + entity("half", 0x00BD); + entity("hamilt", 0x210B); + entity("HARDcy", 0x042A); + entity("hardcy", 0x044A); + entity("hArr", 0x21D4); + entity("harr", 0x2194); + entity("harrcir", 0x2948); + entity("harrw", 0x21AD); + entity("Hat", 0x005E); + entity("hbar", 0x210F); + entity("Hcirc", 0x0124); + entity("hcirc", 0x0125); + entity("hearts", 0x2665); + entity("heartsuit", 0x2665); + entity("hellip", 0x2026); + entity("hercon", 0x22B9); + entity("Hfr", 0x210C); + entity("hfr", 0x1D525); + entity("HilbertSpace", 0x210B); + entity("hksearow", 0x2925); + entity("hkswarow", 0x2926); + entity("hoarr", 0x21FF); + entity("homtht", 0x223B); + entity("hookleftarrow", 0x21A9); + entity("hookrightarrow", 0x21AA); + entity("Hopf", 0x210D); + entity("hopf", 0x1D559); + entity("horbar", 0x2015); + entity("HorizontalLine", 0x2500); + entity("Hscr", 0x210B); + entity("hscr", 0x1D4BD); + entity("hslash", 0x210F); + entity("Hstrok", 0x0126); + entity("hstrok", 0x0127); + entity("HumpDownHump", 0x224E); + entity("HumpEqual", 0x224F); + entity("hybull", 0x2043); + entity("hyphen", 0x2010); + entity("Iacgr", 0x038A); + entity("iacgr", 0x03AF); + entity("Iacute", 0x00CD); + entity("iacute", 0x00ED); + entity("ic", 0x2063); + entity("Icirc", 0x00CE); + entity("icirc", 0x00EE); + entity("Icy", 0x0418); + entity("icy", 0x0438); + entity("idiagr", 0x0390); + entity("Idigr", 0x03AA); + entity("idigr", 0x03CA); + entity("Idot", 0x0130); + entity("IEcy", 0x0415); + entity("iecy", 0x0435); + entity("iexcl", 0x00A1); + entity("iff", 0x21D4); + entity("Ifr", 0x2111); + entity("ifr", 0x1D526); + entity("Igr", 0x0399); + entity("igr", 0x03B9); + entity("Igrave", 0x00CC); + entity("igrave", 0x00EC); + entity("ii", 0x2148); + entity("iiiint", 0x2A0C); + entity("iiint", 0x222D); + entity("iinfin", 0x29DC); + entity("iiota", 0x2129); + entity("IJlig", 0x0132); + entity("ijlig", 0x0133); + entity("Im", 0x2111); + entity("Imacr", 0x012A); + entity("imacr", 0x012B); + entity("image", 0x2111); + entity("ImaginaryI", 0x2148); + entity("imagline", 0x2110); + entity("imagpart", 0x2111); + entity("imath", 0x0131); + entity("imof", 0x22B7); + entity("imped", 0x01B5); + entity("Implies", 0x21D2); + entity("in", 0x2208); + entity("incare", 0x2105); + entity("infin", 0x221E); + entity("infintie", 0x29DD); + entity("inodot", 0x0131); + entity("Int", 0x222C); + entity("int", 0x222B); + entity("intcal", 0x22BA); + entity("integers", 0x2124); + entity("Integral", 0x222B); + entity("intercal", 0x22BA); + entity("Intersection", 0x22C2); + entity("intlarhk", 0x2A17); + entity("intprod", 0x2A3C); + entity("InvisibleComma", 0x2063); + entity("InvisibleTimes", 0x2062); + entity("IOcy", 0x0401); + entity("iocy", 0x0451); + entity("Iogon", 0x012E); + entity("iogon", 0x012F); + entity("Iopf", 0x1D540); + entity("iopf", 0x1D55A); + entity("Iota", 0x0399); + entity("iota", 0x03B9); + entity("iprod", 0x2A3C); + entity("iquest", 0x00BF); + entity("Iscr", 0x2110); + entity("iscr", 0x1D4BE); + entity("isin", 0x2208); + entity("isindot", 0x22F5); + entity("isinE", 0x22F9); + entity("isins", 0x22F4); + entity("isinsv", 0x22F3); + entity("isinv", 0x2208); + entity("it", 0x2062); + entity("Itilde", 0x0128); + entity("itilde", 0x0129); + entity("Iukcy", 0x0406); + entity("iukcy", 0x0456); + entity("Iuml", 0x00CF); + entity("iuml", 0x00EF); + entity("Jcirc", 0x0134); + entity("jcirc", 0x0135); + entity("Jcy", 0x0419); + entity("jcy", 0x0439); + entity("Jfr", 0x1D50D); + entity("jfr", 0x1D527); + entity("jmath", 0x0237); + entity("Jopf", 0x1D541); + entity("jopf", 0x1D55B); + entity("Jscr", 0x1D4A5); + entity("jscr", 0x1D4BF); + entity("Jsercy", 0x0408); + entity("jsercy", 0x0458); + entity("Jukcy", 0x0404); + entity("jukcy", 0x0454); + entity("Kappa", 0x039A); + entity("kappa", 0x03BA); + entity("kappav", 0x03F0); + entity("Kcedil", 0x0136); + entity("kcedil", 0x0137); + entity("Kcy", 0x041A); + entity("kcy", 0x043A); + entity("Kfr", 0x1D50E); + entity("kfr", 0x1D528); + entity("Kgr", 0x039A); + entity("kgr", 0x03BA); + entity("kgreen", 0x0138); + entity("KHcy", 0x0425); + entity("khcy", 0x0445); + entity("KHgr", 0x03A7); + entity("khgr", 0x03C7); + entity("KJcy", 0x040C); + entity("kjcy", 0x045C); + entity("Kopf", 0x1D542); + entity("kopf", 0x1D55C); + entity("Kscr", 0x1D4A6); + entity("kscr", 0x1D4C0); + entity("lAarr", 0x21DA); + entity("Lacute", 0x0139); + entity("lacute", 0x013A); + entity("laemptyv", 0x29B4); + entity("lagran", 0x2112); + entity("Lambda", 0x039B); + entity("lambda", 0x03BB); + entity("Lang", 0x27EA); + entity("lang", 0x2329); + entity("langd", 0x2991); + entity("langle", 0x2329); + entity("lap", 0x2A85); + entity("Laplacetrf", 0x2112); + entity("laquo", 0x00AB); + entity("Larr", 0x219E); + entity("lArr", 0x21D0); + entity("larr", 0x2190); + entity("larrb", 0x21E4); + entity("larrbfs", 0x291F); + entity("larrfs", 0x291D); + entity("larrhk", 0x21A9); + entity("larrlp", 0x21AB); + entity("larrpl", 0x2939); + entity("larrsim", 0x2973); + entity("larrtl", 0x21A2); + entity("lat", 0x2AAB); + entity("lAtail", 0x291B); + entity("latail", 0x2919); + entity("late", 0x2AAD); + entity("lBarr", 0x290E); + entity("lbarr", 0x290C); + entity("lbbrk", 0x2997); + entity("lbrace", 0x007B); + entity("lbrack", 0x005B); + entity("lbrke", 0x298B); + entity("lbrksld", 0x298F); + entity("lbrkslu", 0x298D); + entity("Lcaron", 0x013D); + entity("lcaron", 0x013E); + entity("Lcedil", 0x013B); + entity("lcedil", 0x013C); + entity("lceil", 0x2308); + entity("lcub", 0x007B); + entity("Lcy", 0x041B); + entity("lcy", 0x043B); + entity("ldca", 0x2936); + entity("ldquo", 0x201C); + entity("ldquor", 0x201E); + entity("ldrdhar", 0x2967); + entity("ldrushar", 0x294B); + entity("ldsh", 0x21B2); + entity("lE", 0x2266); + entity("le", 0x2264); + entity("LeftAngleBracket", 0x2329); + entity("Leftarrow", 0x21D0); + entity("leftarrow", 0x2190); + entity("LeftArrowBar", 0x21E4); + entity("LeftArrowRightArrow", 0x21C6); + entity("leftarrowtail", 0x21A2); + entity("LeftCeiling", 0x2308); + entity("LeftDoubleBracket", 0x27E6); + entity("LeftDownTeeVector", 0x2961); + entity("LeftDownVector", 0x21C3); + entity("LeftDownVectorBar", 0x2959); + entity("LeftFloor", 0x230A); + entity("leftharpoondown", 0x21BD); + entity("leftharpoonup", 0x21BC); + entity("leftleftarrows", 0x21C7); + entity("Leftrightarrow", 0x21D4); + entity("leftrightarrow", 0x2194); + entity("leftrightarrows", 0x21C6); + entity("leftrightharpoons", 0x21CB); + entity("leftrightsquigarrow", 0x21AD); + entity("LeftRightVector", 0x294E); + entity("LeftTee", 0x22A3); + entity("LeftTeeArrow", 0x21A4); + entity("LeftTeeVector", 0x295A); + entity("leftthreetimes", 0x22CB); + entity("LeftTriangle", 0x22B2); + entity("LeftTriangleBar", 0x29CF); + entity("LeftTriangleEqual", 0x22B4); + entity("LeftUpDownVector", 0x2951); + entity("LeftUpTeeVector", 0x2960); + entity("LeftUpVector", 0x21BF); + entity("LeftUpVectorBar", 0x2958); + entity("LeftVector", 0x21BC); + entity("LeftVectorBar", 0x2952); + entity("lEg", 0x2A8B); + entity("leg", 0x22DA); + entity("leq", 0x2264); + entity("leqq", 0x2266); + entity("leqslant", 0x2A7D); + entity("les", 0x2A7D); + entity("lescc", 0x2AA8); + entity("lesdot", 0x2A7F); + entity("lesdoto", 0x2A81); + entity("lesdotor", 0x2A83); + entity("lesges", 0x2A93); + entity("lessapprox", 0x2A85); + entity("lessdot", 0x22D6); + entity("lesseqgtr", 0x22DA); + entity("lesseqqgtr", 0x2A8B); + entity("LessEqualGreater", 0x22DA); + entity("LessFullEqual", 0x2266); + entity("LessGreater", 0x2276); + entity("lessgtr", 0x2276); + entity("LessLess", 0x2AA1); + entity("lesssim", 0x2272); + entity("LessSlantEqual", 0x2A7D); + entity("LessTilde", 0x2272); + entity("lfisht", 0x297C); + entity("lfloor", 0x230A); + entity("Lfr", 0x1D50F); + entity("lfr", 0x1D529); + entity("lg", 0x2276); + entity("lgE", 0x2A91); + entity("Lgr", 0x039B); + entity("lgr", 0x03BB); + entity("lHar", 0x2962); + entity("lhard", 0x21BD); + entity("lharu", 0x21BC); + entity("lharul", 0x296A); + entity("lhblk", 0x2584); + entity("LJcy", 0x0409); + entity("ljcy", 0x0459); + entity("Ll", 0x22D8); + entity("ll", 0x226A); + entity("llarr", 0x21C7); + entity("llcorner", 0x231E); + entity("Lleftarrow", 0x21DA); + entity("llhard", 0x296B); + entity("lltri", 0x25FA); + entity("Lmidot", 0x013F); + entity("lmidot", 0x0140); + entity("lmoust", 0x23B0); + entity("lmoustache", 0x23B0); + entity("lnap", 0x2A89); + entity("lnapprox", 0x2A89); + entity("lnE", 0x2268); + entity("lne", 0x2A87); + entity("lneq", 0x2A87); + entity("lneqq", 0x2268); + entity("lnsim", 0x22E6); + entity("loang", 0x27EC); + entity("loarr", 0x21FD); + entity("lobrk", 0x27E6); + entity("Longleftarrow", 0x27F8); + entity("longleftarrow", 0x27F5); + entity("Longleftrightarrow", 0x27FA); + entity("longleftrightarrow", 0x27F7); + entity("longmapsto", 0x27FC); + entity("Longrightarrow", 0x27F9); + entity("longrightarrow", 0x27F6); + entity("looparrowleft", 0x21AB); + entity("looparrowright", 0x21AC); + entity("lopar", 0x2985); + entity("Lopf", 0x1D543); + entity("lopf", 0x1D55D); + entity("loplus", 0x2A2D); + entity("lotimes", 0x2A34); + entity("lowast", 0x2217); + entity("lowbar", 0x005F); + entity("LowerLeftArrow", 0x2199); + entity("LowerRightArrow", 0x2198); + entity("loz", 0x25CA); + entity("lozenge", 0x25CA); + entity("lozf", 0x29EB); + entity("lpar", 0x0028); + entity("lparlt", 0x2993); + entity("lrarr", 0x21C6); + entity("lrcorner", 0x231F); + entity("lrhar", 0x21CB); + entity("lrhard", 0x296D); + entity("lrm", 0x200E); + entity("lrtri", 0x22BF); + entity("lsaquo", 0x2039); + entity("Lscr", 0x2112); + entity("lscr", 0x1D4C1); + entity("lsh", 0x21B0); + entity("lsim", 0x2272); + entity("lsime", 0x2A8D); + entity("lsimg", 0x2A8F); + entity("lsqb", 0x005B); + entity("lsquo", 0x2018); + entity("lsquor", 0x201A); + entity("Lstrok", 0x0141); + entity("lstrok", 0x0142); + entity("Lt", 0x226A); + entity("lt", 0x003C); + entity("ltcc", 0x2AA6); + entity("ltcir", 0x2A79); + entity("ltdot", 0x22D6); + entity("lthree", 0x22CB); + entity("ltimes", 0x22C9); + entity("ltlarr", 0x2976); + entity("ltquest", 0x2A7B); + entity("ltri", 0x25C3); + entity("ltrie", 0x22B4); + entity("ltrif", 0x25C2); + entity("ltrPar", 0x2996); + entity("lurdshar", 0x294A); + entity("luruhar", 0x2966); + entity("macr", 0x00AF); + entity("male", 0x2642); + entity("malt", 0x2720); + entity("maltese", 0x2720); + entity("Map", 0x2905); + entity("map", 0x21A6); + entity("mapsto", 0x21A6); + entity("mapstodown", 0x21A7); + entity("mapstoleft", 0x21A4); + entity("mapstoup", 0x21A5); + entity("marker", 0x25AE); + entity("mcomma", 0x2A29); + entity("Mcy", 0x041C); + entity("mcy", 0x043C); + entity("mdash", 0x2014); + entity("mDDot", 0x223A); + entity("measuredangle", 0x2221); + entity("MediumSpace", 0x205F); + entity("Mellintrf", 0x2133); + entity("Mfr", 0x1D510); + entity("mfr", 0x1D52A); + entity("Mgr", 0x039C); + entity("mgr", 0x03BC); + entity("mho", 0x2127); + entity("micro", 0x00B5); + entity("mid", 0x2223); + entity("midast", 0x002A); + entity("midcir", 0x2AF0); + entity("middot", 0x00B7); + entity("minus", 0x2212); + entity("minusb", 0x229F); + entity("minusd", 0x2238); + entity("minusdu", 0x2A2A); + entity("MinusPlus", 0x2213); + entity("mlcp", 0x2ADB); + entity("mldr", 0x2026); + entity("mnplus", 0x2213); + entity("models", 0x22A7); + entity("Mopf", 0x1D544); + entity("mopf", 0x1D55E); + entity("mp", 0x2213); + entity("Mscr", 0x2133); + entity("mscr", 0x1D4C2); + entity("mstpos", 0x223E); + entity("Mu", 0x039C); + entity("mu", 0x03BC); + entity("multimap", 0x22B8); + entity("mumap", 0x22B8); + entity("nabla", 0x2207); + entity("Nacute", 0x0143); + entity("nacute", 0x0144); + entity("nap", 0x2249); + entity("napos", 0x0149); + entity("napprox", 0x2249); + entity("natur", 0x266E); + entity("natural", 0x266E); + entity("naturals", 0x2115); + entity("nbsp", 0x00A0); + entity("ncap", 0x2A43); + entity("Ncaron", 0x0147); + entity("ncaron", 0x0148); + entity("Ncedil", 0x0145); + entity("ncedil", 0x0146); + entity("ncong", 0x2247); + entity("ncup", 0x2A42); + entity("Ncy", 0x041D); + entity("ncy", 0x043D); + entity("ndash", 0x2013); + entity("ne", 0x2260); + entity("nearhk", 0x2924); + entity("neArr", 0x21D7); + entity("nearr", 0x2197); + entity("nearrow", 0x2197); + entity("NegativeMediumSpace", 0x200B); + entity("NegativeThickSpace", 0x200B); + entity("NegativeThinSpace", 0x200B); + entity("NegativeVeryThinSpace", 0x200B); + entity("nequiv", 0x2262); + entity("nesear", 0x2928); + entity("NestedGreaterGreater", 0x226B); + entity("NestedLessLess", 0x226A); + entity("NewLine", 0x000A); + entity("nexist", 0x2204); + entity("nexists", 0x2204); + entity("Nfr", 0x1D511); + entity("nfr", 0x1D52B); + entity("nge", 0x2271); + entity("ngeq", 0x2271); + entity("Ngr", 0x039D); + entity("ngr", 0x03BD); + entity("ngsim", 0x2275); + entity("ngt", 0x226F); + entity("ngtr", 0x226F); + entity("nhArr", 0x21CE); + entity("nharr", 0x21AE); + entity("nhpar", 0x2AF2); + entity("ni", 0x220B); + entity("nis", 0x22FC); + entity("nisd", 0x22FA); + entity("niv", 0x220B); + entity("NJcy", 0x040A); + entity("njcy", 0x045A); + entity("nlArr", 0x21CD); + entity("nlarr", 0x219A); + entity("nldr", 0x2025); + entity("nle", 0x2270); + entity("nLeftarrow", 0x21CD); + entity("nleftarrow", 0x219A); + entity("nLeftrightarrow", 0x21CE); + entity("nleftrightarrow", 0x21AE); + entity("nleq", 0x2270); + entity("nless", 0x226E); + entity("nlsim", 0x2274); + entity("nlt", 0x226E); + entity("nltri", 0x22EA); + entity("nltrie", 0x22EC); + entity("nmid", 0x2224); + entity("NoBreak", 0x2060); + entity("NonBreakingSpace", 0x00A0); + entity("Nopf", 0x2115); + entity("nopf", 0x1D55F); + entity("Not", 0x2AEC); + entity("not", 0x00AC); + entity("NotCongruent", 0x2262); + entity("NotCupCap", 0x226D); + entity("NotDoubleVerticalBar", 0x2226); + entity("NotElement", 0x2209); + entity("NotEqual", 0x2260); + entity("NotExists", 0x2204); + entity("NotGreater", 0x226F); + entity("NotGreaterEqual", 0x2271); + entity("NotGreaterLess", 0x2279); + entity("NotGreaterTilde", 0x2275); + entity("notin", 0x2209); + entity("notinva", 0x2209); + entity("notinvb", 0x22F7); + entity("notinvc", 0x22F6); + entity("NotLeftTriangle", 0x22EA); + entity("NotLeftTriangleEqual", 0x22EC); + entity("NotLess", 0x226E); + entity("NotLessEqual", 0x2270); + entity("NotLessGreater", 0x2278); + entity("NotLessTilde", 0x2274); + entity("notni", 0x220C); + entity("notniva", 0x220C); + entity("notnivb", 0x22FE); + entity("notnivc", 0x22FD); + entity("NotPrecedes", 0x2280); + entity("NotPrecedesSlantEqual", 0x22E0); + entity("NotReverseElement", 0x220C); + entity("NotRightTriangle", 0x22EB); + entity("NotRightTriangleEqual", 0x22ED); + entity("NotSquareSubsetEqual", 0x22E2); + entity("NotSquareSupersetEqual", 0x22E3); + entity("NotSubsetEqual", 0x2288); + entity("NotSucceeds", 0x2281); + entity("NotSucceedsSlantEqual", 0x22E1); + entity("NotSupersetEqual", 0x2289); + entity("NotTilde", 0x2241); + entity("NotTildeEqual", 0x2244); + entity("NotTildeFullEqual", 0x2247); + entity("NotTildeTilde", 0x2249); + entity("NotVerticalBar", 0x2224); + entity("npar", 0x2226); + entity("nparallel", 0x2226); + entity("npolint", 0x2A14); + entity("npr", 0x2280); + entity("nprcue", 0x22E0); + entity("nprec", 0x2280); + entity("nrArr", 0x21CF); + entity("nrarr", 0x219B); + entity("nRightarrow", 0x21CF); + entity("nrightarrow", 0x219B); + entity("nrtri", 0x22EB); + entity("nrtrie", 0x22ED); + entity("nsc", 0x2281); + entity("nsccue", 0x22E1); + entity("Nscr", 0x1D4A9); + entity("nscr", 0x1D4C3); + entity("nshortmid", 0x2224); + entity("nshortparallel", 0x2226); + entity("nsim", 0x2241); + entity("nsime", 0x2244); + entity("nsimeq", 0x2244); + entity("nsmid", 0x2224); + entity("nspar", 0x2226); + entity("nsqsube", 0x22E2); + entity("nsqsupe", 0x22E3); + entity("nsub", 0x2284); + entity("nsube", 0x2288); + entity("nsubseteq", 0x2288); + entity("nsucc", 0x2281); + entity("nsup", 0x2285); + entity("nsupe", 0x2289); + entity("nsupseteq", 0x2289); + entity("ntgl", 0x2279); + entity("Ntilde", 0x00D1); + entity("ntilde", 0x00F1); + entity("ntlg", 0x2278); + entity("ntriangleleft", 0x22EA); + entity("ntrianglelefteq", 0x22EC); + entity("ntriangleright", 0x22EB); + entity("ntrianglerighteq", 0x22ED); + entity("Nu", 0x039D); + entity("nu", 0x03BD); + entity("num", 0x0023); + entity("numero", 0x2116); + entity("numsp", 0x2007); + entity("nVDash", 0x22AF); + entity("nVdash", 0x22AE); + entity("nvDash", 0x22AD); + entity("nvdash", 0x22AC); + entity("nvHarr", 0x2904); + entity("nvinfin", 0x29DE); + entity("nvlArr", 0x2902); + entity("nvrArr", 0x2903); + entity("nwarhk", 0x2923); + entity("nwArr", 0x21D6); + entity("nwarr", 0x2196); + entity("nwarrow", 0x2196); + entity("nwnear", 0x2927); + entity("Oacgr", 0x038C); + entity("oacgr", 0x03CC); + entity("Oacute", 0x00D3); + entity("oacute", 0x00F3); + entity("oast", 0x229B); + entity("ocir", 0x229A); + entity("Ocirc", 0x00D4); + entity("ocirc", 0x00F4); + entity("Ocy", 0x041E); + entity("ocy", 0x043E); + entity("odash", 0x229D); + entity("Odblac", 0x0150); + entity("odblac", 0x0151); + entity("odiv", 0x2A38); + entity("odot", 0x2299); + entity("odsold", 0x29BC); + entity("OElig", 0x0152); + entity("oelig", 0x0153); + entity("ofcir", 0x29BF); + entity("Ofr", 0x1D512); + entity("ofr", 0x1D52C); + entity("ogon", 0x02DB); + entity("Ogr", 0x039F); + entity("ogr", 0x03BF); + entity("Ograve", 0x00D2); + entity("ograve", 0x00F2); + entity("ogt", 0x29C1); + entity("OHacgr", 0x038F); + entity("ohacgr", 0x03CE); + entity("ohbar", 0x29B5); + entity("OHgr", 0x03A9); + entity("ohgr", 0x03C9); + entity("ohm", 0x2126); + entity("oint", 0x222E); + entity("olarr", 0x21BA); + entity("olcir", 0x29BE); + entity("olcross", 0x29BB); + entity("oline", 0x203E); + entity("olt", 0x29C0); + entity("Omacr", 0x014C); + entity("omacr", 0x014D); + entity("Omega", 0x03A9); + entity("omega", 0x03C9); + entity("Omicron", 0x039F); + entity("omicron", 0x03BF); + entity("omid", 0x29B6); + entity("ominus", 0x2296); + entity("Oopf", 0x1D546); + entity("oopf", 0x1D560); + entity("opar", 0x29B7); + entity("OpenCurlyDoubleQuote", 0x201C); + entity("OpenCurlyQuote", 0x2018); + entity("operp", 0x29B9); + entity("oplus", 0x2295); + entity("Or", 0x2A54); + entity("or", 0x2228); + entity("orarr", 0x21BB); + entity("ord", 0x2A5D); + entity("order", 0x2134); + entity("orderof", 0x2134); + entity("ordf", 0x00AA); + entity("ordm", 0x00BA); + entity("origof", 0x22B6); + entity("oror", 0x2A56); + entity("orslope", 0x2A57); + entity("orv", 0x2A5B); + entity("oS", 0x24C8); + entity("Oscr", 0x1D4AA); + entity("oscr", 0x2134); + entity("Oslash", 0x00D8); + entity("oslash", 0x00F8); + entity("osol", 0x2298); + entity("Otilde", 0x00D5); + entity("otilde", 0x00F5); + entity("Otimes", 0x2A37); + entity("otimes", 0x2297); + entity("otimesas", 0x2A36); + entity("Ouml", 0x00D6); + entity("ouml", 0x00F6); + entity("ovbar", 0x233D); + entity("OverBar", 0x00AF); + entity("OverBrace", 0xFE37); + entity("OverBracket", 0x23B4); + entity("OverParenthesis", 0xFE35); + entity("par", 0x2225); + entity("para", 0x00B6); + entity("parallel", 0x2225); + entity("parsim", 0x2AF3); + entity("parsl", 0x2AFD); + entity("part", 0x2202); + entity("PartialD", 0x2202); + entity("Pcy", 0x041F); + entity("pcy", 0x043F); + entity("percnt", 0x0025); + entity("period", 0x002E); + entity("permil", 0x2030); + entity("perp", 0x22A5); + entity("pertenk", 0x2031); + entity("Pfr", 0x1D513); + entity("pfr", 0x1D52D); + entity("Pgr", 0x03A0); + entity("pgr", 0x03C0); + entity("PHgr", 0x03A6); + entity("phgr", 0x03C6); + entity("Phi", 0x03A6); + entity("phi", 0x03D5); + entity("phiv", 0x03C6); + entity("phmmat", 0x2133); + entity("phone", 0x260E); + entity("Pi", 0x03A0); + entity("pi", 0x03C0); + entity("pitchfork", 0x22D4); + entity("piv", 0x03D6); + entity("planck", 0x210F); + entity("planckh", 0x210E); + entity("plankv", 0x210F); + entity("plus", 0x002B); + entity("plusacir", 0x2A23); + entity("plusb", 0x229E); + entity("pluscir", 0x2A22); + entity("plusdo", 0x2214); + entity("plusdu", 0x2A25); + entity("pluse", 0x2A72); + entity("PlusMinus", 0x00B1); + entity("plusmn", 0x00B1); + entity("plussim", 0x2A26); + entity("plustwo", 0x2A27); + entity("pm", 0x00B1); + entity("Poincareplane", 0x210C); + entity("pointint", 0x2A15); + entity("Popf", 0x2119); + entity("popf", 0x1D561); + entity("pound", 0x00A3); + entity("Pr", 0x2ABB); + entity("pr", 0x227A); + entity("prap", 0x2AB7); + entity("prcue", 0x227C); + entity("prE", 0x2AB3); + entity("pre", 0x2AAF); + entity("prec", 0x227A); + entity("precapprox", 0x2AB7); + entity("preccurlyeq", 0x227C); + entity("Precedes", 0x227A); + entity("PrecedesEqual", 0x2AAF); + entity("PrecedesSlantEqual", 0x227C); + entity("PrecedesTilde", 0x227E); + entity("preceq", 0x2AAF); + entity("precnapprox", 0x2AB9); + entity("precneqq", 0x2AB5); + entity("precnsim", 0x22E8); + entity("precsim", 0x227E); + entity("Prime", 0x2033); + entity("prime", 0x2032); + entity("primes", 0x2119); + entity("prnap", 0x2AB9); + entity("prnE", 0x2AB5); + entity("prnsim", 0x22E8); + entity("prod", 0x220F); + entity("Product", 0x220F); + entity("profalar", 0x232E); + entity("profline", 0x2312); + entity("profsurf", 0x2313); + entity("prop", 0x221D); + entity("Proportion", 0x2237); + entity("Proportional", 0x221D); + entity("propto", 0x221D); + entity("prsim", 0x227E); + entity("prurel", 0x22B0); + entity("Pscr", 0x1D4AB); + entity("pscr", 0x1D4C5); + entity("PSgr", 0x03A8); + entity("psgr", 0x03C8); + entity("Psi", 0x03A8); + entity("psi", 0x03C8); + entity("puncsp", 0x2008); + entity("Qfr", 0x1D514); + entity("qfr", 0x1D52E); + entity("qint", 0x2A0C); + entity("Qopf", 0x211A); + entity("qopf", 0x1D562); + entity("qprime", 0x2057); + entity("Qscr", 0x1D4AC); + entity("qscr", 0x1D4C6); + entity("quaternions", 0x210D); + entity("quatint", 0x2A16); + entity("quest", 0x003F); + entity("questeq", 0x225F); + entity("quot", 0x0022); + entity("rAarr", 0x21DB); + entity("race", 0x29DA); + entity("Racute", 0x0154); + entity("racute", 0x0155); + entity("radic", 0x221A); + entity("raemptyv", 0x29B3); + entity("Rang", 0x27EB); + entity("rang", 0x232A); + entity("rangd", 0x2992); + entity("range", 0x29A5); + entity("rangle", 0x232A); + entity("raquo", 0x00BB); + entity("Rarr", 0x21A0); + entity("rArr", 0x21D2); + entity("rarr", 0x2192); + entity("rarrap", 0x2975); + entity("rarrb", 0x21E5); + entity("rarrbfs", 0x2920); + entity("rarrc", 0x2933); + entity("rarrfs", 0x291E); + entity("rarrhk", 0x21AA); + entity("rarrlp", 0x21AC); + entity("rarrpl", 0x2945); + entity("rarrsim", 0x2974); + entity("Rarrtl", 0x2916); + entity("rarrtl", 0x21A3); + entity("rarrw", 0x219D); + entity("rAtail", 0x291C); + entity("ratail", 0x291A); + entity("ratio", 0x2236); + entity("rationals", 0x211A); + entity("RBarr", 0x2910); + entity("rBarr", 0x290F); + entity("rbarr", 0x290D); + entity("rbbrk", 0x2998); + entity("rbrace", 0x007D); + entity("rbrack", 0x005D); + entity("rbrke", 0x298C); + entity("rbrksld", 0x298E); + entity("rbrkslu", 0x2990); + entity("Rcaron", 0x0158); + entity("rcaron", 0x0159); + entity("Rcedil", 0x0156); + entity("rcedil", 0x0157); + entity("rceil", 0x2309); + entity("rcub", 0x007D); + entity("Rcy", 0x0420); + entity("rcy", 0x0440); + entity("rdca", 0x2937); + entity("rdldhar", 0x2969); + entity("rdquo", 0x201D); + entity("rdquor", 0x201D); + entity("rdsh", 0x21B3); + entity("Re", 0x211C); + entity("real", 0x211C); + entity("realine", 0x211B); + entity("realpart", 0x211C); + entity("reals", 0x211D); + entity("rect", 0x25AD); + entity("reg", 0x00AE); + entity("ReverseElement", 0x220B); + entity("ReverseEquilibrium", 0x21CB); + entity("ReverseUpEquilibrium", 0x296F); + entity("rfisht", 0x297D); + entity("rfloor", 0x230B); + entity("Rfr", 0x211C); + entity("rfr", 0x1D52F); + entity("Rgr", 0x03A1); + entity("rgr", 0x03C1); + entity("rHar", 0x2964); + entity("rhard", 0x21C1); + entity("rharu", 0x21C0); + entity("rharul", 0x296C); + entity("Rho", 0x03A1); + entity("rho", 0x03C1); + entity("rhov", 0x03F1); + entity("RightAngleBracket", 0x232A); + entity("Rightarrow", 0x21D2); + entity("rightarrow", 0x2192); + entity("RightArrowBar", 0x21E5); + entity("RightArrowLeftArrow", 0x21C4); + entity("rightarrowtail", 0x21A3); + entity("RightCeiling", 0x2309); + entity("RightDoubleBracket", 0x27E7); + entity("RightDownTeeVector", 0x295D); + entity("RightDownVector", 0x21C2); + entity("RightDownVectorBar", 0x2955); + entity("RightFloor", 0x230B); + entity("rightharpoondown", 0x21C1); + entity("rightharpoonup", 0x21C0); + entity("rightleftarrows", 0x21C4); + entity("rightleftharpoons", 0x21CC); + entity("rightrightarrows", 0x21C9); + entity("rightsquigarrow", 0x219D); + entity("RightTee", 0x22A2); + entity("RightTeeArrow", 0x21A6); + entity("RightTeeVector", 0x295B); + entity("rightthreetimes", 0x22CC); + entity("RightTriangle", 0x22B3); + entity("RightTriangleBar", 0x29D0); + entity("RightTriangleEqual", 0x22B5); + entity("RightUpDownVector", 0x294F); + entity("RightUpTeeVector", 0x295C); + entity("RightUpVector", 0x21BE); + entity("RightUpVectorBar", 0x2954); + entity("RightVector", 0x21C0); + entity("RightVectorBar", 0x2953); + entity("ring", 0x02DA); + entity("risingdotseq", 0x2253); + entity("rlarr", 0x21C4); + entity("rlhar", 0x21CC); + entity("rlm", 0x200F); + entity("rmoust", 0x23B1); + entity("rmoustache", 0x23B1); + entity("rnmid", 0x2AEE); + entity("roang", 0x27ED); + entity("roarr", 0x21FE); + entity("robrk", 0x27E7); + entity("ropar", 0x2986); + entity("Ropf", 0x211D); + entity("ropf", 0x1D563); + entity("roplus", 0x2A2E); + entity("rotimes", 0x2A35); + entity("RoundImplies", 0x2970); + entity("rpar", 0x0029); + entity("rpargt", 0x2994); + entity("rppolint", 0x2A12); + entity("rrarr", 0x21C9); + entity("Rrightarrow", 0x21DB); + entity("rsaquo", 0x203A); + entity("Rscr", 0x211B); + entity("rscr", 0x1D4C7); + entity("rsh", 0x21B1); + entity("rsqb", 0x005D); + entity("rsquo", 0x2019); + entity("rsquor", 0x2019); + entity("rthree", 0x22CC); + entity("rtimes", 0x22CA); + entity("rtri", 0x25B9); + entity("rtrie", 0x22B5); + entity("rtrif", 0x25B8); + entity("rtriltri", 0x29CE); + entity("RuleDelayed", 0x29F4); + entity("ruluhar", 0x2968); + entity("rx", 0x211E); + entity("Sacute", 0x015A); + entity("sacute", 0x015B); + entity("sbquo", 0x201A); + entity("Sc", 0x2ABC); + entity("sc", 0x227B); + entity("scap", 0x2AB8); + entity("Scaron", 0x0160); + entity("scaron", 0x0161); + entity("sccue", 0x227D); + entity("scE", 0x2AB4); + entity("sce", 0x2AB0); + entity("Scedil", 0x015E); + entity("scedil", 0x015F); + entity("Scirc", 0x015C); + entity("scirc", 0x015D); + entity("scnap", 0x2ABA); + entity("scnE", 0x2AB6); + entity("scnsim", 0x22E9); + entity("scpolint", 0x2A13); + entity("scsim", 0x227F); + entity("Scy", 0x0421); + entity("scy", 0x0441); + entity("sdot", 0x22C5); + entity("sdotb", 0x22A1); + entity("sdote", 0x2A66); + entity("searhk", 0x2925); + entity("seArr", 0x21D8); + entity("searr", 0x2198); + entity("searrow", 0x2198); + entity("sect", 0x00A7); + entity("semi", 0x003B); + entity("seswar", 0x2929); + entity("setminus", 0x2216); + entity("setmn", 0x2216); + entity("sext", 0x2736); + entity("sfgr", 0x03C2); + entity("Sfr", 0x1D516); + entity("sfr", 0x1D530); + entity("sfrown", 0x2322); + entity("Sgr", 0x03A3); + entity("sgr", 0x03C3); + entity("sharp", 0x266F); + entity("SHCHcy", 0x0429); + entity("shchcy", 0x0449); + entity("SHcy", 0x0428); + entity("shcy", 0x0448); + entity("ShortDownArrow", 0x2193); + entity("ShortLeftArrow", 0x2190); + entity("shortmid", 0x2223); + entity("shortparallel", 0x2225); + entity("ShortRightArrow", 0x2192); + entity("ShortUpArrow", 0x2191); + entity("shy", 0x00AD); + entity("Sigma", 0x03A3); + entity("sigma", 0x03C3); + entity("sigmaf", 0x03C2); + entity("sigmav", 0x03C2); + entity("sim", 0x223C); + entity("simdot", 0x2A6A); + entity("sime", 0x2243); + entity("simeq", 0x2243); + entity("simg", 0x2A9E); + entity("simgE", 0x2AA0); + entity("siml", 0x2A9D); + entity("simlE", 0x2A9F); + entity("simne", 0x2246); + entity("simplus", 0x2A24); + entity("simrarr", 0x2972); + entity("slarr", 0x2190); + entity("SmallCircle", 0x2218); + entity("smallsetminus", 0x2216); + entity("smashp", 0x2A33); + entity("smeparsl", 0x29E4); + entity("smid", 0x2223); + entity("smile", 0x2323); + entity("smt", 0x2AAA); + entity("smte", 0x2AAC); + entity("SOFTcy", 0x042C); + entity("softcy", 0x044C); + entity("sol", 0x002F); + entity("solb", 0x29C4); + entity("solbar", 0x233F); + entity("Sopf", 0x1D54A); + entity("sopf", 0x1D564); + entity("spades", 0x2660); + entity("spadesuit", 0x2660); + entity("spar", 0x2225); + entity("sqcap", 0x2293); + entity("sqcup", 0x2294); + entity("Sqrt", 0x221A); + entity("sqsub", 0x228F); + entity("sqsube", 0x2291); + entity("sqsubset", 0x228F); + entity("sqsubseteq", 0x2291); + entity("sqsup", 0x2290); + entity("sqsupe", 0x2292); + entity("sqsupset", 0x2290); + entity("sqsupseteq", 0x2292); + entity("squ", 0x25A1); + entity("square", 0x25A1); + entity("SquareIntersection", 0x2293); + entity("SquareSubset", 0x228F); + entity("SquareSubsetEqual", 0x2291); + entity("SquareSuperset", 0x2290); + entity("SquareSupersetEqual", 0x2292); + entity("SquareUnion", 0x2294); + entity("squarf", 0x25AA); + entity("squf", 0x25AA); + entity("srarr", 0x2192); + entity("Sscr", 0x1D4AE); + entity("sscr", 0x1D4C8); + entity("ssetmn", 0x2216); + entity("ssmile", 0x2323); + entity("sstarf", 0x22C6); + entity("Star", 0x22C6); + entity("star", 0x2606); + entity("starf", 0x2605); + entity("straightepsilon", 0x03F5); + entity("straightphi", 0x03D5); + entity("strns", 0x00AF); + entity("Sub", 0x22D0); + entity("sub", 0x2282); + entity("subdot", 0x2ABD); + entity("subE", 0x2AC5); + entity("sube", 0x2286); + entity("subedot", 0x2AC3); + entity("submult", 0x2AC1); + entity("subnE", 0x2ACB); + entity("subne", 0x228A); + entity("subplus", 0x2ABF); + entity("subrarr", 0x2979); + entity("Subset", 0x22D0); + entity("subset", 0x2282); + entity("subseteq", 0x2286); + entity("subseteqq", 0x2AC5); + entity("SubsetEqual", 0x2286); + entity("subsetneq", 0x228A); + entity("subsetneqq", 0x2ACB); + entity("subsim", 0x2AC7); + entity("subsub", 0x2AD5); + entity("subsup", 0x2AD3); + entity("succ", 0x227B); + entity("succapprox", 0x2AB8); + entity("succcurlyeq", 0x227D); + entity("Succeeds", 0x227B); + entity("SucceedsEqual", 0x2AB0); + entity("SucceedsSlantEqual", 0x227D); + entity("SucceedsTilde", 0x227F); + entity("succeq", 0x2AB0); + entity("succnapprox", 0x2ABA); + entity("succneqq", 0x2AB6); + entity("succnsim", 0x22E9); + entity("succsim", 0x227F); + entity("SuchThat", 0x220B); + entity("sum", 0x2211); + entity("sung", 0x266A); + entity("Sup", 0x22D1); + entity("sup", 0x2283); + entity("sup1", 0x00B9); + entity("sup2", 0x00B2); + entity("sup3", 0x00B3); + entity("supdot", 0x2ABE); + entity("supdsub", 0x2AD8); + entity("supE", 0x2AC6); + entity("supe", 0x2287); + entity("supedot", 0x2AC4); + entity("Superset", 0x2283); + entity("SupersetEqual", 0x2287); + entity("suphsub", 0x2AD7); + entity("suplarr", 0x297B); + entity("supmult", 0x2AC2); + entity("supnE", 0x2ACC); + entity("supne", 0x228B); + entity("supplus", 0x2AC0); + entity("Supset", 0x22D1); + entity("supset", 0x2283); + entity("supseteq", 0x2287); + entity("supseteqq", 0x2AC6); + entity("supsetneq", 0x228B); + entity("supsetneqq", 0x2ACC); + entity("supsim", 0x2AC8); + entity("supsub", 0x2AD4); + entity("supsup", 0x2AD6); + entity("swarhk", 0x2926); + entity("swArr", 0x21D9); + entity("swarr", 0x2199); + entity("swarrow", 0x2199); + entity("swnwar", 0x292A); + entity("szlig", 0x00DF); + entity("Tab", 0x0009); + entity("target", 0x2316); + entity("Tau", 0x03A4); + entity("tau", 0x03C4); + entity("tbrk", 0x23B4); + entity("Tcaron", 0x0164); + entity("tcaron", 0x0165); + entity("Tcedil", 0x0162); + entity("tcedil", 0x0163); + entity("Tcy", 0x0422); + entity("tcy", 0x0442); + entity("telrec", 0x2315); + entity("Tfr", 0x1D517); + entity("tfr", 0x1D531); + entity("Tgr", 0x03A4); + entity("tgr", 0x03C4); + entity("there4", 0x2234); + entity("therefore", 0x2234); + entity("Theta", 0x0398); + entity("theta", 0x03B8); + entity("thetasym", 0x03D1); + entity("thetav", 0x03D1); + entity("THgr", 0x0398); + entity("thgr", 0x03B8); + entity("thickapprox", 0x2248); + entity("thicksim", 0x223C); + entity("thinsp", 0x2009); + entity("ThinSpace", 0x2009); + entity("thkap", 0x2248); + entity("thksim", 0x223C); + entity("THORN", 0x00DE); + entity("thorn", 0x00FE); + entity("Tilde", 0x223C); + entity("tilde", 0x02DC); + entity("TildeEqual", 0x2243); + entity("TildeFullEqual", 0x2245); + entity("TildeTilde", 0x2248); + entity("times", 0x00D7); + entity("timesb", 0x22A0); + entity("timesbar", 0x2A31); + entity("timesd", 0x2A30); + entity("tint", 0x222D); + entity("toea", 0x2928); + entity("top", 0x22A4); + entity("topbot", 0x2336); + entity("topcir", 0x2AF1); + entity("Topf", 0x1D54B); + entity("topf", 0x1D565); + entity("topfork", 0x2ADA); + entity("tosa", 0x2929); + entity("tprime", 0x2034); + entity("trade", 0x2122); + entity("triangle", 0x25B5); + entity("triangledown", 0x25BF); + entity("triangleleft", 0x25C3); + entity("trianglelefteq", 0x22B4); + entity("triangleq", 0x225C); + entity("triangleright", 0x25B9); + entity("trianglerighteq", 0x22B5); + entity("tridot", 0x25EC); + entity("trie", 0x225C); + entity("triminus", 0x2A3A); + entity("triplus", 0x2A39); + entity("trisb", 0x29CD); + entity("tritime", 0x2A3B); + entity("trpezium", 0x23E2); + entity("Tscr", 0x1D4AF); + entity("tscr", 0x1D4C9); + entity("TScy", 0x0426); + entity("tscy", 0x0446); + entity("TSHcy", 0x040B); + entity("tshcy", 0x045B); + entity("Tstrok", 0x0166); + entity("tstrok", 0x0167); + entity("twixt", 0x226C); + entity("twoheadleftarrow", 0x219E); + entity("twoheadrightarrow", 0x21A0); + entity("Uacgr", 0x038E); + entity("uacgr", 0x03CD); + entity("Uacute", 0x00DA); + entity("uacute", 0x00FA); + entity("Uarr", 0x219F); + entity("uArr", 0x21D1); + entity("uarr", 0x2191); + entity("Uarrocir", 0x2949); + entity("Ubrcy", 0x040E); + entity("ubrcy", 0x045E); + entity("Ubreve", 0x016C); + entity("ubreve", 0x016D); + entity("Ucirc", 0x00DB); + entity("ucirc", 0x00FB); + entity("Ucy", 0x0423); + entity("ucy", 0x0443); + entity("udarr", 0x21C5); + entity("Udblac", 0x0170); + entity("udblac", 0x0171); + entity("udhar", 0x296E); + entity("udiagr", 0x03B0); + entity("Udigr", 0x03AB); + entity("udigr", 0x03CB); + entity("ufisht", 0x297E); + entity("Ufr", 0x1D518); + entity("ufr", 0x1D532); + entity("Ugr", 0x03A5); + entity("ugr", 0x03C5); + entity("Ugrave", 0x00D9); + entity("ugrave", 0x00F9); + entity("uHar", 0x2963); + entity("uharl", 0x21BF); + entity("uharr", 0x21BE); + entity("uhblk", 0x2580); + entity("ulcorn", 0x231C); + entity("ulcorner", 0x231C); + entity("ulcrop", 0x230F); + entity("ultri", 0x25F8); + entity("Umacr", 0x016A); + entity("umacr", 0x016B); + entity("uml", 0x00A8); + entity("UnderBrace", 0xFE38); + entity("UnderBracket", 0x23B5); + entity("UnderParenthesis", 0xFE36); + entity("Union", 0x22C3); + entity("UnionPlus", 0x228E); + entity("Uogon", 0x0172); + entity("uogon", 0x0173); + entity("Uopf", 0x1D54C); + entity("uopf", 0x1D566); + entity("Uparrow", 0x21D1); + entity("uparrow", 0x2191); + entity("UpArrowBar", 0x2912); + entity("UpArrowDownArrow", 0x21C5); + entity("Updownarrow", 0x21D5); + entity("updownarrow", 0x2195); + entity("UpEquilibrium", 0x296E); + entity("upharpoonleft", 0x21BF); + entity("upharpoonright", 0x21BE); + entity("uplus", 0x228E); + entity("UpperLeftArrow", 0x2196); + entity("UpperRightArrow", 0x2197); + entity("Upsi", 0x03D2); + entity("upsi", 0x03C5); + entity("upsih", 0x03D2); + entity("Upsilon", 0x03A5); + entity("upsilon", 0x03C5); + entity("UpTee", 0x22A5); + entity("UpTeeArrow", 0x21A5); + entity("upuparrows", 0x21C8); + entity("urcorn", 0x231D); + entity("urcorner", 0x231D); + entity("urcrop", 0x230E); + entity("Uring", 0x016E); + entity("uring", 0x016F); + entity("urtri", 0x25F9); + entity("Uscr", 0x1D4B0); + entity("uscr", 0x1D4CA); + entity("utdot", 0x22F0); + entity("Utilde", 0x0168); + entity("utilde", 0x0169); + entity("utri", 0x25B5); + entity("utrif", 0x25B4); + entity("uuarr", 0x21C8); + entity("Uuml", 0x00DC); + entity("uuml", 0x00FC); + entity("uwangle", 0x29A7); + entity("vangrt", 0x299C); + entity("varepsilon", 0x03B5); + entity("varkappa", 0x03F0); + entity("varnothing", 0x2205); + entity("varphi", 0x03C6); + entity("varpi", 0x03D6); + entity("varpropto", 0x221D); + entity("vArr", 0x21D5); + entity("varr", 0x2195); + entity("varrho", 0x03F1); + entity("varsigma", 0x03C2); + entity("vartheta", 0x03D1); + entity("vartriangleleft", 0x22B2); + entity("vartriangleright", 0x22B3); + entity("Vbar", 0x2AEB); + entity("vBar", 0x2AE8); + entity("vBarv", 0x2AE9); + entity("Vcy", 0x0412); + entity("vcy", 0x0432); + entity("VDash", 0x22AB); + entity("Vdash", 0x22A9); + entity("vDash", 0x22A8); + entity("vdash", 0x22A2); + entity("Vdashl", 0x2AE6); + entity("Vee", 0x22C1); + entity("vee", 0x2228); + entity("veebar", 0x22BB); + entity("veeeq", 0x225A); + entity("vellip", 0x22EE); + entity("Verbar", 0x2016); + entity("verbar", 0x007C); + entity("Vert", 0x2016); + entity("vert", 0x007C); + entity("VerticalBar", 0x2223); + entity("VerticalLine", 0x007C); + entity("VerticalSeparator", 0x2758); + entity("VerticalTilde", 0x2240); + entity("VeryThinSpace", 0x200A); + entity("Vfr", 0x1D519); + entity("vfr", 0x1D533); + entity("vltri", 0x22B2); + entity("Vopf", 0x1D54D); + entity("vopf", 0x1D567); + entity("vprop", 0x221D); + entity("vrtri", 0x22B3); + entity("Vscr", 0x1D4B1); + entity("vscr", 0x1D4CB); + entity("Vvdash", 0x22AA); + entity("vzigzag", 0x299A); + entity("Wcirc", 0x0174); + entity("wcirc", 0x0175); + entity("wedbar", 0x2A5F); + entity("Wedge", 0x22C0); + entity("wedge", 0x2227); + entity("wedgeq", 0x2259); + entity("weierp", 0x2118); + entity("Wfr", 0x1D51A); + entity("wfr", 0x1D534); + entity("Wopf", 0x1D54E); + entity("wopf", 0x1D568); + entity("wp", 0x2118); + entity("wr", 0x2240); + entity("wreath", 0x2240); + entity("Wscr", 0x1D4B2); + entity("wscr", 0x1D4CC); + entity("xcap", 0x22C2); + entity("xcirc", 0x25EF); + entity("xcup", 0x22C3); + entity("xdtri", 0x25BD); + entity("Xfr", 0x1D51B); + entity("xfr", 0x1D535); + entity("Xgr", 0x039E); + entity("xgr", 0x03BE); + entity("xhArr", 0x27FA); + entity("xharr", 0x27F7); + entity("Xi", 0x039E); + entity("xi", 0x03BE); + entity("xlArr", 0x27F8); + entity("xlarr", 0x27F5); + entity("xmap", 0x27FC); + entity("xnis", 0x22FB); + entity("xodot", 0x2A00); + entity("Xopf", 0x1D54F); + entity("xopf", 0x1D569); + entity("xoplus", 0x2A01); + entity("xotime", 0x2A02); + entity("xrArr", 0x27F9); + entity("xrarr", 0x27F6); + entity("Xscr", 0x1D4B3); + entity("xscr", 0x1D4CD); + entity("xsqcup", 0x2A06); + entity("xuplus", 0x2A04); + entity("xutri", 0x25B3); + entity("xvee", 0x22C1); + entity("xwedge", 0x22C0); + entity("Yacute", 0x00DD); + entity("yacute", 0x00FD); + entity("YAcy", 0x042F); + entity("yacy", 0x044F); + entity("Ycirc", 0x0176); + entity("ycirc", 0x0177); + entity("Ycy", 0x042B); + entity("ycy", 0x044B); + entity("yen", 0x00A5); + entity("Yfr", 0x1D51C); + entity("yfr", 0x1D536); + entity("YIcy", 0x0407); + entity("yicy", 0x0457); + entity("Yopf", 0x1D550); + entity("yopf", 0x1D56A); + entity("Yscr", 0x1D4B4); + entity("yscr", 0x1D4CE); + entity("YUcy", 0x042E); + entity("yucy", 0x044E); + entity("Yuml", 0x0178); + entity("yuml", 0x00FF); + entity("Zacute", 0x0179); + entity("zacute", 0x017A); + entity("Zcaron", 0x017D); + entity("zcaron", 0x017E); + entity("Zcy", 0x0417); + entity("zcy", 0x0437); + entity("Zdot", 0x017B); + entity("zdot", 0x017C); + entity("zeetrf", 0x2128); + entity("ZeroWidthSpace", 0x200B); + entity("Zeta", 0x0396); + entity("zeta", 0x03B6); + entity("Zfr", 0x2128); + entity("zfr", 0x1D537); + entity("Zgr", 0x0396); + entity("zgr", 0x03B6); + entity("ZHcy", 0x0416); + entity("zhcy", 0x0436); + entity("zigrarr", 0x21DD); + entity("Zopf", 0x2124); + entity("zopf", 0x1D56B); + entity("Zscr", 0x1D4B5); + entity("zscr", 0x1D4CF); + entity("zwj", 0x200D); + entity("zwnj", 0x200C); + // End of Schema calls + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/PYXWriter.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/PYXWriter.java new file mode 100755 index 0000000000..1d41a5aa71 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/PYXWriter.java @@ -0,0 +1,204 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// PYX Writer +// FIXME: does not do escapes in attribute values +// FIXME: outputs entities as bare '&' character +package org.ccil.cowan.tagsoup; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.ext.LexicalHandler; + +import java.io.PrintWriter; +import java.io.Writer; +/** + A ContentHandler that generates PYX format instead of XML. + Primarily useful for debugging. + **/ +public class PYXWriter + implements ScanHandler, ContentHandler, LexicalHandler { + private PrintWriter theWriter; // where we write to + private static char[] dummy = new char[1]; + private String attrName; // saved attribute name + // ScanHandler implementation + public void adup(char[] buff, int offset, int length) throws SAXException { + theWriter.println(attrName); + attrName = null; + } + public void aname(char[] buff, int offset, int length) throws SAXException { + theWriter.print('A'); + theWriter.write(buff, offset, length); + theWriter.print(' '); + attrName = new String(buff, offset, length); + } + public void aval(char[] buff, int offset, int length) throws SAXException { + theWriter.write(buff, offset, length); + theWriter.println(); + attrName = null; + } + public void cmnt(char [] buff, int offset, int length) throws SAXException { +// theWriter.print('!'); +// theWriter.write(buff, offset, length); +// theWriter.println(); + } + public void entity(char[] buff, int offset, int length) throws SAXException { } + public int getEntity() { return 0; } + public void eof(char[] buff, int offset, int length) throws SAXException { + theWriter.close(); + } + public void etag(char[] buff, int offset, int length) throws SAXException { + theWriter.print(')'); + theWriter.write(buff, offset, length); + theWriter.println(); + } + public void decl(char[] buff, int offset, int length) throws SAXException { + } + public void gi(char[] buff, int offset, int length) throws SAXException { + theWriter.print('('); + theWriter.write(buff, offset, length); + theWriter.println(); + } + public void cdsect(char[] buff, int offset, int length) throws SAXException { + pcdata(buff, offset, length); + } + public void pcdata(char[] buff, int offset, int length) throws SAXException { + if (length == 0) return; // nothing to do + boolean inProgress = false; + length += offset; + for (int i = offset; i < length; i++) { + if (buff[i] == '\n') { + if (inProgress) { + theWriter.println(); + } + theWriter.println("-\\n"); + inProgress = false; + } + else { + if (!inProgress) { + theWriter.print('-'); + } + switch(buff[i]) { + case '\t': + theWriter.print("\\t"); + break; + case '\\': + theWriter.print("\\\\"); + break; + default: + theWriter.print(buff[i]); + } + inProgress = true; + } + } + if (inProgress) { + theWriter.println(); + } + } + public void pitarget(char[] buff, int offset, int length) throws SAXException { + theWriter.print('?'); + theWriter.write(buff, offset, length); + theWriter.write(' '); + } + public void pi(char[] buff, int offset, int length) throws SAXException { + theWriter.write(buff, offset, length); + theWriter.println(); + } + public void stagc(char[] buff, int offset, int length) throws SAXException { +// theWriter.println("!"); // FIXME + } + public void stage(char[] buff, int offset, int length) throws SAXException { + theWriter.println("!"); // FIXME + } + // SAX ContentHandler implementation + public void characters(char[] buff, int offset, int length) throws SAXException { + pcdata(buff, offset, length); + } + public void endDocument() throws SAXException { + theWriter.close(); + } + public void endElement(String uri, String localname, String qname) throws SAXException { + if (qname.length() == 0) qname = localname; + theWriter.print(')'); + theWriter.println(qname); + } + public void endPrefixMapping(String prefix) throws SAXException { } + public void ignorableWhitespace(char[] buff, int offset, int length) throws SAXException { + characters(buff, offset, length); + } + public void processingInstruction(String target, String data) throws SAXException { + theWriter.print('?'); + theWriter.print(target); + theWriter.print(' '); + theWriter.println(data); + } + public void setDocumentLocator(Locator locator) { } + public void skippedEntity(String name) throws SAXException { } + public void startDocument() throws SAXException { } + public void startElement(String uri, String localname, String qname, + Attributes atts) throws SAXException { + if (qname.length() == 0) qname=localname; + theWriter.print('('); + theWriter.println(qname); + int length = atts.getLength(); + for (int i = 0; i < length; i++) { + qname = atts.getQName(i); + if (qname.length() == 0) qname = atts.getLocalName(i); + theWriter.print('A'); +// theWriter.print(atts.getType(i)); // DEBUG + theWriter.print(qname); + theWriter.print(' '); + theWriter.println(atts.getValue(i)); + } + } + public void startPrefixMapping(String prefix, String uri) throws SAXException { } + // Default LexicalHandler implementation + public void comment(char[] ch, int start, int length) throws SAXException { + cmnt(ch, start, length); + } + public void endCDATA() throws SAXException { } + public void endDTD() throws SAXException { } + public void endEntity(String name) throws SAXException { } + public void startCDATA() throws SAXException { } + public void startDTD(String name, String publicId, String systemId) throws SAXException { } + public void startEntity(String name) throws SAXException { } + // Constructor + public PYXWriter(Writer w) { + if (w instanceof PrintWriter) { + theWriter = (PrintWriter)w; + } + else { + theWriter = new PrintWriter(w); + } + } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Parser.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Parser.java new file mode 100755 index 0000000000..6ae8e23e03 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Parser.java @@ -0,0 +1,1043 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// The TagSoup parser +package org.ccil.cowan.tagsoup; + +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.DTDHandler; +import org.xml.sax.EntityResolver; +import org.xml.sax.ErrorHandler; +import org.xml.sax.InputSource; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXNotRecognizedException; +import org.xml.sax.SAXNotSupportedException; +import org.xml.sax.XMLReader; +import org.xml.sax.ext.LexicalHandler; +import org.xml.sax.helpers.DefaultHandler; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Locale; + +/** + The SAX parser class. + **/ +public class Parser extends DefaultHandler implements ScanHandler, XMLReader, LexicalHandler { + // XMLReader implementation + private ContentHandler theContentHandler = this; + private LexicalHandler theLexicalHandler = this; + private DTDHandler theDTDHandler = this; + private ErrorHandler theErrorHandler = this; + private EntityResolver theEntityResolver = this; + private Schema theSchema; + private Scanner theScanner; + private AutoDetector theAutoDetector; + // Default values for feature flags + private static boolean DEFAULT_NAMESPACES = true; + private static boolean DEFAULT_IGNORE_BOGONS = false; + private static boolean DEFAULT_BOGONS_EMPTY = false; + private static boolean DEFAULT_ROOT_BOGONS = true; + private static boolean DEFAULT_DEFAULT_ATTRIBUTES = true; + private static boolean DEFAULT_TRANSLATE_COLONS = false; + private static boolean DEFAULT_RESTART_ELEMENTS = true; + private static boolean DEFAULT_IGNORABLE_WHITESPACE = false; + private static boolean DEFAULT_CDATA_ELEMENTS = true; + // Feature flags. + private boolean namespaces = DEFAULT_NAMESPACES; + private boolean ignoreBogons = DEFAULT_IGNORE_BOGONS; + private boolean bogonsEmpty = DEFAULT_BOGONS_EMPTY; + private boolean rootBogons = DEFAULT_ROOT_BOGONS; + private boolean defaultAttributes = DEFAULT_DEFAULT_ATTRIBUTES; + private boolean translateColons = DEFAULT_TRANSLATE_COLONS; + private boolean restartElements = DEFAULT_RESTART_ELEMENTS; + private boolean ignorableWhitespace = DEFAULT_IGNORABLE_WHITESPACE; + private boolean CDATAElements = DEFAULT_CDATA_ELEMENTS; + /** + A value of "true" indicates namespace URIs and unprefixed local + names for element and attribute names will be available. + **/ + public final static String namespacesFeature = + "http://xml.org/sax/features/namespaces"; + /** + A value of "true" indicates that XML qualified names (with prefixes) + and attributes (including xmlns* attributes) will be available. + We don't support this value. + **/ + public final static String namespacePrefixesFeature = + "http://xml.org/sax/features/namespace-prefixes"; + /** + Reports whether this parser processes external general entities + (it doesn't). + **/ + public final static String externalGeneralEntitiesFeature = + "http://xml.org/sax/features/external-general-entities"; + /** + Reports whether this parser processes external parameter entities + (it doesn't). + **/ + public final static String externalParameterEntitiesFeature = + "http://xml.org/sax/features/external-parameter-entities"; + /** + May be examined only during a parse, after the startDocument() + callback has been completed; read-only. The value is true if + the document specified standalone="yes" in its XML declaration, + and otherwise is false. (It's always false.) + **/ + public final static String isStandaloneFeature = + "http://xml.org/sax/features/is-standalone"; + /** + A value of "true" indicates that the LexicalHandler will report + the beginning and end of parameter entities (it won't). + **/ + public final static String lexicalHandlerParameterEntitiesFeature = + "http://xml.org/sax/features/lexical-handler/parameter-entities"; + /** + A value of "true" indicates that system IDs in declarations will + be absolutized (relative to their base URIs) before reporting. + (This returns true but doesn't actually do anything.) + **/ + public final static String resolveDTDURIsFeature = + "http://xml.org/sax/features/resolve-dtd-uris"; + /** + Has a value of "true" if all XML names (for elements, + prefixes, attributes, entities, notations, and local + names), as well as Namespace URIs, will have been interned + using java.lang.String.intern. This supports fast testing of + equality/inequality against string constants, rather than forcing + slower calls to String.equals(). (We always intern.) + **/ + public final static String stringInterningFeature = + "http://xml.org/sax/features/string-interning"; + /** + Returns "true" if the Attributes objects passed by this + parser in ContentHandler.startElement() implement the + org.xml.sax.ext.Attributes2 interface. (They don't.) + **/ + public final static String useAttributes2Feature = + "http://xml.org/sax/features/use-attributes2"; + /** + Returns "true" if the Locator objects passed by this parser + in ContentHandler.setDocumentLocator() implement the + org.xml.sax.ext.Locator2 interface. (They don't.) + **/ + public final static String useLocator2Feature = + "http://xml.org/sax/features/use-locator2"; + /** + Returns "true" if, when setEntityResolver is given an object + implementing the org.xml.sax.ext.EntityResolver2 interface, + those new methods will be used. (They won't be.) + **/ + public final static String useEntityResolver2Feature = + "http://xml.org/sax/features/use-entity-resolver2"; + /** + Controls whether the parser is reporting all validity errors + (We don't report any validity errors.) + **/ + public final static String validationFeature = + "http://xml.org/sax/features/validation"; + /** + Controls whether the parser reports Unicode normalization + errors as described in section 2.13 and Appendix B of the XML + 1.1 Recommendation. (We don't normalize.) + **/ + public final static String unicodeNormalizationCheckingFeature = + "http://xml.org/sax/features/unicode-normalization-checking"; + /** + Controls whether, when the namespace-prefixes feature is set, + the parser treats namespace declaration attributes as being in + the http://www.w3.org/2000/xmlns/ namespace. (It doesn't.) + **/ + public final static String xmlnsURIsFeature = + "http://xml.org/sax/features/xmlns-uris"; + /** + Returns "true" if the parser supports both XML 1.1 and XML 1.0. + (Always false.) + **/ + public final static String XML11Feature = + "http://xml.org/sax/features/xml-1.1"; + /** + A value of "true" indicates that the parser will ignore + unknown elements. + **/ + public final static String ignoreBogonsFeature = + "http://www.ccil.org/~cowan/tagsoup/features/ignore-bogons"; + /** + A value of "true" indicates that the parser will give unknown + elements a content model of EMPTY; a value of "false", a + content model of ANY. + **/ + public final static String bogonsEmptyFeature = + "http://www.ccil.org/~cowan/tagsoup/features/bogons-empty"; + /** + A value of "true" indicates that the parser will allow unknown + elements to be the root element. + **/ + public final static String rootBogonsFeature = + "http://www.ccil.org/~cowan/tagsoup/features/root-bogons"; + /** + A value of "true" indicates that the parser will return default + attribute values for missing attributes that have default values. + **/ + public final static String defaultAttributesFeature = + "http://www.ccil.org/~cowan/tagsoup/features/default-attributes"; + /** + A value of "true" indicates that the parser will + translate colons into underscores in names. + **/ + public final static String translateColonsFeature = + "http://www.ccil.org/~cowan/tagsoup/features/translate-colons"; + /** + A value of "true" indicates that the parser will + attempt to restart the restartable elements. + **/ + public final static String restartElementsFeature = + "http://www.ccil.org/~cowan/tagsoup/features/restart-elements"; + /** + A value of "true" indicates that the parser will + transmit whitespace in element-only content via the SAX + ignorableWhitespace callback. Normally this is not done, + because HTML is an SGML application and SGML suppresses + such whitespace. + **/ + public final static String ignorableWhitespaceFeature = + "http://www.ccil.org/~cowan/tagsoup/features/ignorable-whitespace"; + /** + A value of "true" indicates that the parser will treat CDATA + elements specially. Normally true, since the input is by + default HTML. + **/ + public final static String CDATAElementsFeature = + "http://www.ccil.org/~cowan/tagsoup/features/cdata-elements"; + /** + Used to see some syntax events that are essential in some + applications: comments, CDATA delimiters, selected general + entity inclusions, and the start and end of the DTD (and + declaration of document element name). The Object must implement + org.xml.sax.ext.LexicalHandler. + **/ + public final static String lexicalHandlerProperty = + "http://xml.org/sax/properties/lexical-handler"; + /** + Specifies the Scanner object this Parser uses. + **/ + public final static String scannerProperty = + "http://www.ccil.org/~cowan/tagsoup/properties/scanner"; + /** + Specifies the Schema object this Parser uses. + **/ + public final static String schemaProperty = + "http://www.ccil.org/~cowan/tagsoup/properties/schema"; + /** + Specifies the AutoDetector (for encoding detection) this Parser uses. + **/ + public final static String autoDetectorProperty = + "http://www.ccil.org/~cowan/tagsoup/properties/auto-detector"; + // Due to sucky Java order of initialization issues, these + // entries are maintained separately from the initial values of + // the corresponding instance variables, but care must be taken + // to keep them in sync. + private HashMap theFeatures = new HashMap(); + { + theFeatures.put(namespacesFeature, truthValue(DEFAULT_NAMESPACES)); + theFeatures.put(namespacePrefixesFeature, Boolean.FALSE); + theFeatures.put(externalGeneralEntitiesFeature, Boolean.FALSE); + theFeatures.put(externalParameterEntitiesFeature, Boolean.FALSE); + theFeatures.put(isStandaloneFeature, Boolean.FALSE); + theFeatures.put(lexicalHandlerParameterEntitiesFeature, + Boolean.FALSE); + theFeatures.put(resolveDTDURIsFeature, Boolean.TRUE); + theFeatures.put(stringInterningFeature, Boolean.TRUE); + theFeatures.put(useAttributes2Feature, Boolean.FALSE); + theFeatures.put(useLocator2Feature, Boolean.FALSE); + theFeatures.put(useEntityResolver2Feature, Boolean.FALSE); + theFeatures.put(validationFeature, Boolean.FALSE); + theFeatures.put(xmlnsURIsFeature, Boolean.FALSE); + theFeatures.put(xmlnsURIsFeature, Boolean.FALSE); + theFeatures.put(XML11Feature, Boolean.FALSE); + theFeatures.put(ignoreBogonsFeature, truthValue(DEFAULT_IGNORE_BOGONS)); + theFeatures.put(bogonsEmptyFeature, truthValue(DEFAULT_BOGONS_EMPTY)); + theFeatures.put(rootBogonsFeature, truthValue(DEFAULT_ROOT_BOGONS)); + theFeatures.put(defaultAttributesFeature, truthValue(DEFAULT_DEFAULT_ATTRIBUTES)); + theFeatures.put(translateColonsFeature, truthValue(DEFAULT_TRANSLATE_COLONS)); + theFeatures.put(restartElementsFeature, truthValue(DEFAULT_RESTART_ELEMENTS)); + theFeatures.put(ignorableWhitespaceFeature, truthValue(DEFAULT_IGNORABLE_WHITESPACE)); + theFeatures.put(CDATAElementsFeature, truthValue(DEFAULT_CDATA_ELEMENTS)); + } + // Private clone of Boolean.valueOf that is guaranteed to return + // Boolean.TRUE or Boolean.FALSE + private static Boolean truthValue(boolean b) { + return b ? Boolean.TRUE : Boolean.FALSE; + } + public boolean getFeature (String name) + throws SAXNotRecognizedException, SAXNotSupportedException { + Boolean b = (Boolean)theFeatures.get(name); + if (b == null) { + throw new SAXNotRecognizedException("Unknown feature " + name); + } + return b.booleanValue(); + } + public void setFeature (String name, boolean value) + throws SAXNotRecognizedException, SAXNotSupportedException { + Boolean b = (Boolean)theFeatures.get(name); + if (b == null) { + throw new SAXNotRecognizedException("Unknown feature " + name); + } + if (value) theFeatures.put(name, Boolean.TRUE); + else theFeatures.put(name, Boolean.FALSE); + if (name.equals(namespacesFeature)) namespaces = value; + else if (name.equals(ignoreBogonsFeature)) ignoreBogons = value; + else if (name.equals(bogonsEmptyFeature)) bogonsEmpty = value; + else if (name.equals(rootBogonsFeature)) rootBogons = value; + else if (name.equals(defaultAttributesFeature)) defaultAttributes = value; + else if (name.equals(translateColonsFeature)) translateColons = value; + else if (name.equals(restartElementsFeature)) restartElements = value; + else if (name.equals(ignorableWhitespaceFeature)) ignorableWhitespace = value; + else if (name.equals(CDATAElementsFeature)) CDATAElements = value; + } + public Object getProperty (String name) + throws SAXNotRecognizedException, SAXNotSupportedException { + if (name.equals(lexicalHandlerProperty)) { + return theLexicalHandler == this ? null : theLexicalHandler; + } + else if (name.equals(scannerProperty)) { + return theScanner; + } + else if (name.equals(schemaProperty)) { + return theSchema; + } + else if (name.equals(autoDetectorProperty)) { + return theAutoDetector; + } + else { + throw new SAXNotRecognizedException("Unknown property " + name); + } + } + public void setProperty (String name, Object value) + throws SAXNotRecognizedException, SAXNotSupportedException { + if (name.equals(lexicalHandlerProperty)) { + if (value == null) { + theLexicalHandler = this; + } + else if (value instanceof LexicalHandler) { + theLexicalHandler = (LexicalHandler)value; + } + else { + throw new SAXNotSupportedException("Your lexical handler is not a LexicalHandler"); + } + } + else if (name.equals(scannerProperty)) { + if (value instanceof Scanner) { + theScanner = (Scanner)value; + } + else { + throw new SAXNotSupportedException("Your scanner is not a Scanner"); + } + } + else if (name.equals(schemaProperty)) { + if (value instanceof Schema) { + theSchema = (Schema)value; + } + else { + throw new SAXNotSupportedException("Your schema is not a Schema"); + } + } + else if (name.equals(autoDetectorProperty)) { + if (value instanceof AutoDetector) { + theAutoDetector = (AutoDetector)value; + } + else { + throw new SAXNotSupportedException("Your auto-detector is not an AutoDetector"); + } + } + else { + throw new SAXNotRecognizedException("Unknown property " + name); + } + } + public void setEntityResolver (EntityResolver resolver) { + theEntityResolver = (resolver == null) ? this : resolver; + } + public EntityResolver getEntityResolver () { + return (theEntityResolver == this) ? null : theEntityResolver; + } + public void setDTDHandler (DTDHandler handler) { + theDTDHandler = (handler == null) ? this : handler; + } + public DTDHandler getDTDHandler () { + return (theDTDHandler == this) ? null : theDTDHandler; + } + public void setContentHandler (ContentHandler handler) { + theContentHandler = (handler == null) ? this : handler; + } + public ContentHandler getContentHandler () { + return (theContentHandler == this) ? null : theContentHandler; + } + public void setErrorHandler (ErrorHandler handler) { + theErrorHandler = (handler == null) ? this : handler; + } + public ErrorHandler getErrorHandler () { + return (theErrorHandler == this) ? null : theErrorHandler; + } + public void parse (InputSource input) throws IOException, SAXException { + setup(); + Reader r = getReader(input); + theContentHandler.startDocument(); + theScanner.resetDocumentLocator(input.getPublicId(), input.getSystemId()); + if (theScanner instanceof Locator) { + theContentHandler.setDocumentLocator((Locator)theScanner); + } + if (!(theSchema.getURI().equals(""))) + theContentHandler.startPrefixMapping(theSchema.getPrefix(), + theSchema.getURI()); + theScanner.scan(r, this); + } + public void parse (String systemid) throws IOException, SAXException { + parse(new InputSource(systemid)); + } + // Sets up instance variables that haven't been set by setFeature + private void setup() { + if (theSchema == null) theSchema = new HTMLSchema(); + if (theScanner == null) theScanner = new HTMLScanner(); + if (theAutoDetector == null) { + theAutoDetector = new AutoDetector() { + public Reader autoDetectingReader(InputStream i) { + return new InputStreamReader(i); + } + }; + } + theStack = new Element(theSchema.getElementType(""), defaultAttributes); + thePCDATA = new Element(theSchema.getElementType(""), defaultAttributes); + theNewElement = null; + theAttributeName = null; + thePITarget = null; + theSaved = null; + theEntity = 0; + virginStack = true; + theDoctypeName = theDoctypePublicId = theDoctypeSystemId = null; + } + // Return a Reader based on the contents of an InputSource + // Buffer both the InputStream and the Reader + private Reader getReader(InputSource s) throws SAXException, IOException { + Reader r = s.getCharacterStream(); + InputStream i = s.getByteStream(); + String encoding = s.getEncoding(); + String publicid = s.getPublicId(); + String systemid = s.getSystemId(); + if (r == null) { + if (i == null) i = getInputStream(publicid, systemid); +// i = new BufferedInputStream(i); + if (encoding == null) { + r = theAutoDetector.autoDetectingReader(i); + } + else { + try { + r = new InputStreamReader(i, encoding); + } + catch (UnsupportedEncodingException e) { + r = new InputStreamReader(i); + } + } + } +// r = new BufferedReader(r); + return r; + } + // Get an InputStream based on a publicid and a systemid + private InputStream getInputStream(String publicid, String systemid) throws IOException, SAXException { + URL basis = new URL("file", "", System.getProperty("user.dir") + "/."); + URL url = new URL(basis, systemid); + URLConnection c = url.openConnection(); + return c.getInputStream(); + } + // We don't process publicids (who uses them anyhow?) + // ScanHandler implementation + private Element theNewElement = null; + private String theAttributeName = null; + private boolean theDoctypeIsPresent = false; + private String theDoctypePublicId = null; + private String theDoctypeSystemId = null; + private String theDoctypeName = null; + private String thePITarget = null; + private Element theStack = null; + private Element theSaved = null; + private Element thePCDATA = null; + private int theEntity = 0; // needs to support chars past U+FFFF + public void adup(char[] buff, int offset, int length) throws SAXException { + if (theNewElement == null || theAttributeName == null) return; + theNewElement.setAttribute(theAttributeName, null, theAttributeName); + theAttributeName = null; + } + public void aname(char[] buff, int offset, int length) throws SAXException { + if (theNewElement == null) return; + // Currently we don't rely on Schema to canonicalize + // attribute names. + theAttributeName = makeName(buff, offset, length).toLowerCase(Locale.ROOT); +// System.err.println("%% Attribute name " + theAttributeName); + } + public void aval(char[] buff, int offset, int length) throws SAXException { + if (theNewElement == null || theAttributeName == null) return; + String value = new String(buff, offset, length); +// System.err.println("%% Attribute value [" + value + "]"); + value = expandEntities(value); + theNewElement.setAttribute(theAttributeName, null, value); + theAttributeName = null; +// System.err.println("%% Aval done"); + } + // Expand entity references in attribute values selectively. + // Currently we expand a reference iff it is properly terminated + // with a semicolon. + private String expandEntities(String src) { + int refStart = -1; + int len = src.length(); + char[] dst = new char[len]; + int dstlen = 0; + for (int i = 0; i < len; i++) { + char ch = src.charAt(i); + dst[dstlen++] = ch; +// System.err.print("i = " + i + ", d = " + dstlen + ", ch = [" + ch + "] "); + if (ch == '&' && refStart == -1) { + // start of a ref excluding & + refStart = dstlen; +// System.err.println("start of ref"); + } + else if (refStart == -1) { + // not in a ref +// System.err.println("not in ref"); + } + else if (Character.isLetter(ch) || + Character.isDigit(ch) || + ch == '#') { + // valid entity char +// System.err.println("valid"); + } + else if (ch == ';') { + // properly terminated ref +// System.err.print("got [" + new String(dst, refStart, dstlen-refStart-1) + "]"); + int ent = lookupEntity(dst, refStart, dstlen - refStart - 1); +// System.err.println(" = " + ent); + if (ent > 0xFFFF) { + ent -= 0x10000; + dst[refStart - 1] = (char)((ent>>10) + 0xD800); + dst[refStart] = (char)((ent&0x3FF) + 0xDC00); + dstlen = refStart + 1; + } + else if (ent != 0) { + dst[refStart - 1] = (char)ent; + dstlen = refStart; + } + refStart = -1; + } + else { + // improperly terminated ref +// System.err.println("end of ref"); + refStart = -1; + } + } + return new String(dst, 0, dstlen); + } + public void entity(char[] buff, int offset, int length) throws SAXException { + theEntity = lookupEntity(buff, offset, length); + } + // Process numeric character references, + // deferring to the schema for named ones. + private int lookupEntity(char[] buff, int offset, int length) { + int result = 0; + if (length < 1) return result; +// System.err.println("%% Entity at " + offset + " " + length); +// System.err.println("%% Got entity [" + new String(buff, offset, length) + "]"); + if (buff[offset] == '#') { + if (length > 1 && (buff[offset+1] == 'x' + || buff[offset+1] == 'X')) { + try { + return Integer.parseInt(new String(buff, offset + 2, length - 2), 16); + } + catch (NumberFormatException e) { return 0; } + } + try { + return Integer.parseInt(new String(buff, offset + 1, length - 1), 10); + } + catch (NumberFormatException e) { return 0; } + } + return theSchema.getEntity(new String(buff, offset, length)); + } + public void eof(char[] buff, int offset, int length) throws SAXException { + if (virginStack) rectify(thePCDATA); + while (theStack.next() != null) { + pop(); + } + if (!(theSchema.getURI().equals(""))) + theContentHandler.endPrefixMapping(theSchema.getPrefix()); + theContentHandler.endDocument(); + } + public void etag(char[] buff, int offset, int length) throws SAXException { + if (etag_cdata(buff, offset, length)) return; + etag_basic(buff, offset, length); + } + private static char[] etagchars = {'<', '/', '>'}; + public boolean etag_cdata(char[] buff, int offset, int length) throws SAXException { + String currentName = theStack.name(); + // If this is a CDATA element and the tag doesn't match, + // or isn't properly formed (junk after the name), + // restart CDATA mode and process the tag as characters. + if (CDATAElements && (theStack.flags() & Schema.F_CDATA) != 0) { + boolean realTag = (length == currentName.length()); + if (realTag) { + for (int i = 0; i < length; i++) { + if (Character.toLowerCase(buff[offset + i]) != Character.toLowerCase(currentName.charAt(i))) { + realTag = false; + break; + } + } + } + if (!realTag) { + theContentHandler.characters(etagchars, 0, 2); + theContentHandler.characters(buff, offset, length); + theContentHandler.characters(etagchars, 2, 1); + theScanner.startCDATA(); + return true; + } + } + return false; + } + public void etag_basic(char[] buff, int offset, int length) throws SAXException { + theNewElement = null; + String name; + if (length != 0) { + // Canonicalize case of name + name = makeName(buff, offset, length); +// System.err.println("got etag [" + name + "]"); + ElementType type = theSchema.getElementType(name); + if (type == null) return; // mysterious end-tag + name = type.name(); + } + else { + name = theStack.name(); + } +// System.err.println("%% Got end of " + name); + Element sp; + boolean inNoforce = false; + for (sp = theStack; sp != null; sp = sp.next()) { + if (sp.name().equals(name)) break; + if ((sp.flags() & Schema.F_NOFORCE) != 0) inNoforce = true; + } + if (sp == null) return; // Ignore unknown etags + if (sp.next() == null || sp.next().next() == null) return; + if (inNoforce) { // inside an F_NOFORCE element? + sp.preclose(); // preclose the matching element + } + else { // restartably pop everything above us + while (theStack != sp) { + restartablyPop(); + } + pop(); + } + // pop any preclosed elements now at the top + while (theStack.isPreclosed()) { + pop(); + } + restart(null); + } + // Push restartables on the stack if possible + // e is the next element to be started, if we know what it is + private void restart(Element e) throws SAXException { + while (theSaved != null && theStack.canContain(theSaved) && + (e == null || theSaved.canContain(e))) { + Element next = theSaved.next(); + push(theSaved); + theSaved = next; + } + } + // Pop the stack irrevocably + private void pop() throws SAXException { + if (theStack == null) return; // empty stack + String name = theStack.name(); + String localName = theStack.localName(); + String namespace = theStack.namespace(); + String prefix = prefixOf(name); +// System.err.println("%% Popping " + name); + if (!namespaces) namespace = localName = ""; + theContentHandler.endElement(namespace, localName, name); + if (foreign(prefix, namespace)) { + theContentHandler.endPrefixMapping(prefix); +// System.err.println("%% Unmapping [" + prefix + "] for elements to " + namespace); + } + Attributes atts = theStack.atts(); + for (int i = atts.getLength() - 1; i >= 0; i--) { + String attNamespace = atts.getURI(i); + String attPrefix = prefixOf(atts.getQName(i)); + if (foreign(attPrefix, attNamespace)) { + theContentHandler.endPrefixMapping(attPrefix); +// System.err.println("%% Unmapping [" + attPrefix + "] for attributes to " + attNamespace); + } + } + theStack = theStack.next(); + } + // Pop the stack restartably + private void restartablyPop() throws SAXException { + Element popped = theStack; + pop(); + if (restartElements && (popped.flags() & Schema.F_RESTART) != 0) { + popped.anonymize(); + popped.setNext(theSaved); + theSaved = popped; + } + } + // Push element onto stack + private boolean virginStack = true; + private void push(Element e) throws SAXException { + String name = e.name(); + String localName = e.localName(); + String namespace = e.namespace(); + String prefix = prefixOf(name); +// System.err.println("%% Pushing " + name); + e.clean(); + if (!namespaces) namespace = localName = ""; + if (virginStack && localName.equalsIgnoreCase(theDoctypeName)) { + try { + theEntityResolver.resolveEntity(theDoctypePublicId, theDoctypeSystemId); + } catch (IOException ew) { } // Can't be thrown for root I believe. + } + if (foreign(prefix, namespace)) { + theContentHandler.startPrefixMapping(prefix, namespace); +// System.err.println("%% Mapping [" + prefix + "] for elements to " + namespace); + } + Attributes atts = e.atts(); + int len = atts.getLength(); + for (int i = 0; i < len; i++) { + String attNamespace = atts.getURI(i); + String attPrefix = prefixOf(atts.getQName(i)); + if (foreign(attPrefix, attNamespace)) { + theContentHandler.startPrefixMapping(attPrefix, attNamespace); +// System.err.println("%% Mapping [" + attPrefix + "] for attributes to " + attNamespace); + } + } + theContentHandler.startElement(namespace, localName, name, e.atts()); + e.setNext(theStack); + theStack = e; + virginStack = false; + if (CDATAElements && (theStack.flags() & Schema.F_CDATA) != 0) { + theScanner.startCDATA(); + } + } + // Get the prefix from a QName + private String prefixOf(String name) { + int i = name.indexOf(':'); + String prefix = ""; + if (i != -1) prefix = name.substring(0, i); +// System.err.println("%% " + prefix + " is prefix of " + name); + return prefix; + } + // Return true if we have a foreign name + private boolean foreign(String prefix, String namespace) { +// System.err.print("%% Testing " + prefix + " and " + namespace + " for foreignness -- "); + boolean foreign = !(prefix.equals("") || namespace.equals("") || + namespace.equals(theSchema.getURI())); +// System.err.println(foreign); + return foreign; + } + /** + * Parsing the complete XML Document Type Definition is way too complex, + * but for many simple cases we can extract something useful from it. + */ + public void decl(char[] buff, int offset, int length) throws SAXException { + String s = new String(buff, offset, length); + String name = null; + String systemid = null; + String publicid = null; + String[] v = split(s); + if (v.length > 0 && "DOCTYPE".equalsIgnoreCase(v[0])) { + if (theDoctypeIsPresent) return; // one doctype only! + theDoctypeIsPresent = true; + if (v.length > 1) { + name = v[1]; + if (v.length>3 && "SYSTEM".equals(v[2])) { + systemid = v[3]; + } + else if (v.length > 3 && "PUBLIC".equals(v[2])) { + publicid = v[3]; + if (v.length > 4) { + systemid = v[4]; + } + else { + systemid = ""; + } + } + } + } + publicid = trimquotes(publicid); + systemid = trimquotes(systemid); + if (name != null) { + publicid = cleanPublicid(publicid); + theLexicalHandler.startDTD(name, publicid, systemid); + theLexicalHandler.endDTD(); + theDoctypeName = name; + theDoctypePublicId = publicid; + if (theScanner instanceof Locator) { // Must resolve systemid + theDoctypeSystemId = ((Locator)theScanner).getSystemId(); + try { + theDoctypeSystemId = new URL(new URL(theDoctypeSystemId), systemid).toString(); + } catch (Exception e) {} + } + } + } + // If the String is quoted, trim the quotes. + private static String trimquotes(String in) { + if (in == null) return in; + int length = in.length(); + if (length == 0) return in; + char s = in.charAt(0); + char e = in.charAt(length - 1); + if (s == e && (s == '\'' || s == '"')) { + in = in.substring(1, in.length() - 1); + } + return in; + } + // Split the supplied String into words or phrases seperated by spaces. + // Recognises quotes around a phrase and doesn't split it. + private static String[] split(String val) throws IllegalArgumentException { + val = val.trim(); + if (val.length() == 0) { + return new String[0]; + } + else { + ArrayList l = new ArrayList(); + int s = 0; + int e = 0; + boolean sq = false; // single quote + boolean dq = false; // double quote + char lastc = 0; + int len = val.length(); + for (e=0; e < len; e++) { + char c = val.charAt(e); + if (!dq && c == '\'' && lastc != '\\') { + sq = !sq; + if (s < 0) s = e; + } + else if (!sq && c == '\"' && lastc != '\\') { + dq = !dq; + if (s < 0) s = e; + } + else if (!sq && !dq) { + if (Character.isWhitespace(c)) { + if (s >= 0) l.add(val.substring(s, e)); + s = -1; + } + else if (s < 0 && c != ' ') { + s = e; + } + } + lastc = c; + } + l.add(val.substring(s, e)); + return (String[])l.toArray(new String[0]); + } + } + // Replace junk in publicids with spaces + private static String legal = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-'()+,./:=?;!*#@$_%"; + private String cleanPublicid(String src) { + if (src == null) return null; + int len = src.length(); + StringBuffer dst = new StringBuffer(len); + boolean suppressSpace = true; + for (int i = 0; i < len; i++) { + char ch = src.charAt(i); + if (legal.indexOf(ch) != -1) { // legal but not whitespace + dst.append(ch); + suppressSpace = false; + } + else if (suppressSpace) { // normalizable whitespace or junk + ; + } + else { + dst.append(' '); + suppressSpace = true; + } + } +// System.err.println("%% Publicid [" + dst.toString().trim() + "]"); + return dst.toString().trim(); // trim any final junk whitespace + } + public void gi(char[] buff, int offset, int length) throws SAXException { + if (theNewElement != null) return; + String name = makeName(buff, offset, length); + if (name == null) return; + ElementType type = theSchema.getElementType(name); + if (type == null) { + // Suppress unknown elements if ignore-bogons is on + if (ignoreBogons) return; + int bogonModel = bogonsEmpty ? Schema.M_EMPTY : Schema.M_ANY; + int bogonMemberOf = rootBogons ? Schema.M_ANY : (Schema.M_ANY & ~ Schema.M_ROOT); + theSchema.elementType(name, bogonModel, bogonMemberOf, 0); + if (!rootBogons) theSchema.parent(name, theSchema.rootElementType().name()); + type = theSchema.getElementType(name); + } + theNewElement = new Element(type, defaultAttributes); +// System.err.println("%% Got GI " + theNewElement.name()); + } + public void cdsect(char[] buff, int offset, int length) throws SAXException { + theLexicalHandler.startCDATA(); + pcdata(buff, offset, length); + theLexicalHandler.endCDATA(); + } + public void pcdata(char[] buff, int offset, int length) throws SAXException { + if (length == 0) return; + boolean allWhite = true; + for (int i = 0; i < length; i++) { + if (!Character.isWhitespace(buff[offset+i])) { + allWhite = false; + } + } + if (allWhite && !theStack.canContain(thePCDATA)) { + if (ignorableWhitespace) { + theContentHandler.ignorableWhitespace(buff, offset, length); + } + } + else { + rectify(thePCDATA); + theContentHandler.characters(buff, offset, length); + } + } + public void pitarget(char[] buff, int offset, int length) throws SAXException { + if (theNewElement != null) return; + thePITarget = makeName(buff, offset, length).replace(':', '_'); + } + public void pi(char[] buff, int offset, int length) throws SAXException { + if (theNewElement != null || thePITarget == null) return; + if ("xml".equalsIgnoreCase(thePITarget)) return; +// if (length > 0 && buff[length - 1] == '?') System.err.println("%% Removing ? from PI"); + if (length > 0 && buff[length - 1] == '?') length--; // remove trailing ? + theContentHandler.processingInstruction(thePITarget, + new String(buff, offset, length)); + thePITarget = null; + } + public void stagc(char[] buff, int offset, int length) throws SAXException { +// System.err.println("%% Start-tag"); + if (theNewElement == null) return; + rectify(theNewElement); + if (theStack.model() == Schema.M_EMPTY) { + // Force an immediate end tag + etag_basic(buff, offset, length); + } + } + public void stage(char[] buff, int offset, int length) throws SAXException { +// System.err.println("%% Empty-tag"); + if (theNewElement == null) return; + rectify(theNewElement); + // Force an immediate end tag + etag_basic(buff, offset, length); + } + // Comment buffer is twice the size of the output buffer + private char[] theCommentBuffer = new char[2000]; + public void cmnt(char[] buff, int offset, int length) throws SAXException { + theLexicalHandler.comment(buff, offset, length); + } + // Rectify the stack, pushing and popping as needed + // so that the argument can be safely pushed + private void rectify(Element e) throws SAXException { + Element sp; + while (true) { + for (sp = theStack; sp != null; sp = sp.next()) { + if (sp.canContain(e)) break; + } + if (sp != null) break; + ElementType parentType = e.parent(); + if (parentType == null) break; + Element parent = new Element(parentType, defaultAttributes); +// System.err.println("%% Ascending from " + e.name() + " to " + parent.name()); + parent.setNext(e); + e = parent; + } + if (sp == null) return; // don't know what to do + while (theStack != sp) { + if (theStack == null || theStack.next() == null || + theStack.next().next() == null) break; + restartablyPop(); + } + while (e != null) { + Element nexte = e.next(); + if (!e.name().equals("")) push(e); + e = nexte; + restart(e); + } + theNewElement = null; + } + public int getEntity() { + return theEntity; + } + // Return the argument as a valid XML name + // This no longer lowercases the result: we depend on Schema to + // canonicalize case. + private String makeName(char[] buff, int offset, int length) { + StringBuffer dst = new StringBuffer(length + 2); + boolean seenColon = false; + boolean start = true; +// String src = new String(buff, offset, length); // DEBUG + for (; length-- > 0; offset++) { + char ch = buff[offset]; + if (Character.isLetter(ch) || ch == '_') { + start = false; + dst.append(ch); + } + else if (Character.isDigit(ch) || ch == '-' || ch == '.') { + if (start) dst.append('_'); + start = false; + dst.append(ch); + } + else if (ch == ':' && !seenColon) { + seenColon = true; + if (start) dst.append('_'); + start = true; + dst.append(translateColons ? '_' : ch); + } + } + int dstLength = dst.length(); + if (dstLength == 0 || dst.charAt(dstLength - 1) == ':') dst.append('_'); +// System.err.println("Made name \"" + dst + "\" from \"" + src + "\""); + return dst.toString().intern(); + } + // Default LexicalHandler implementation + public void comment(char[] ch, int start, int length) throws SAXException { } + public void endCDATA() throws SAXException { } + public void endDTD() throws SAXException { } + public void endEntity(String name) throws SAXException { } + public void startCDATA() throws SAXException { } + public void startDTD(String name, String publicid, String systemid) throws SAXException { } + public void startEntity(String name) throws SAXException { } +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ScanHandler.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ScanHandler.java new file mode 100755 index 0000000000..dd3de8afdd --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/ScanHandler.java @@ -0,0 +1,104 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// Scanner handler +package org.ccil.cowan.tagsoup; +import org.xml.sax.SAXException; +/** + An interface that Scanners use to report events in the input stream. + **/ +public interface ScanHandler { + /** + Reports an attribute name without a value. + **/ + public void adup(char[] buff, int offset, int length) throws SAXException; + /** + Reports an attribute name; a value will follow. + **/ + public void aname(char[] buff, int offset, int length) throws SAXException; + /** + Reports an attribute value. + **/ + public void aval(char[] buff, int offset, int length) throws SAXException; + /** + * Reports the content of a CDATA section (not a CDATA element) + */ + public void cdsect(char[] buff, int offset, int length) throws SAXException; + /** + * Reports a declaration - typically a DOCTYPE + */ + public void decl(char[] buff, int offset, int length) throws SAXException; + /** + Reports an entity reference or character reference. + **/ + public void entity(char[] buff, int offset, int length) throws SAXException; + /** + Reports EOF. + **/ + public void eof(char[] buff, int offset, int length) throws SAXException; + /** + Reports an end-tag. + **/ + public void etag(char[] buff, int offset, int length) throws SAXException; + /** + Reports the general identifier (element type name) of a start-tag. + **/ + public void gi(char[] buff, int offset, int length) throws SAXException; + /** + Reports character content. + **/ + public void pcdata(char[] buff, int offset, int length) throws SAXException; + /** + Reports the data part of a processing instruction. + **/ + public void pi(char[] buff, int offset, int length) throws SAXException; + /** + Reports the target part of a processing instruction. + **/ + public void pitarget(char[] buff, int offset, int length) throws SAXException; + /** + Reports the close of a start-tag. + **/ + public void stagc(char[] buff, int offset, int length) throws SAXException; + /** + Reports the close of an empty-tag. + **/ + public void stage(char[] buff, int offset, int length) throws SAXException; + /** + Reports a comment. + **/ + public void cmnt(char[] buff, int offset, int length) throws SAXException; + /** + Returns the value of the last entity or character reference reported. + **/ + public int getEntity(); +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Scanner.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Scanner.java new file mode 100755 index 0000000000..ad5eecf94c --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Scanner.java @@ -0,0 +1,59 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// Scanner +package org.ccil.cowan.tagsoup; +import org.xml.sax.SAXException; + +import java.io.IOException; +import java.io.Reader; +/** + An interface allowing Parser to invoke scanners. + **/ +public interface Scanner { + /** + Invoke a scanner. + @param r A source of characters to scan + @param h A ScanHandler to report events to + **/ + public void scan(Reader r, ScanHandler h) throws IOException, SAXException; + /** + Reset the embedded locator. + @param publicid The publicid of the source + @param systemid The systemid of the source + **/ + public void resetDocumentLocator(String publicid, String systemid); + /** + Signal to the scanner to start CDATA content mode. + **/ + public void startCDATA(); +} \ No newline at end of file diff --git a/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Schema.java b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Schema.java new file mode 100755 index 0000000000..fbec9b87a4 --- /dev/null +++ b/android/sdk/src/main/java/org/ccil/cowan/tagsoup/Schema.java @@ -0,0 +1,158 @@ +/** + * 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. + */ +// This file is part of TagSoup and is Copyright 2002-2008 by John Cowan. +// +// TagSoup is licensed under the Apache License, +// Version 2.0. You may obtain a copy of this license at +// http://www.apache.org/licenses/LICENSE-2.0 . You may also have +// additional legal rights not granted by this license. +// +// TagSoup is distributed in the hope that it will be useful, but +// unless required by applicable law or agreed to in writing, TagSoup +// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS +// OF ANY KIND, either express or implied; not even the implied warranty +// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// +// +// Model of document +package org.ccil.cowan.tagsoup; +import java.util.HashMap; +import java.util.Locale; +/** + Abstract class representing a TSSL schema. + Actual TSSL schemas are compiled into concrete subclasses of this class. + **/ +public abstract class Schema { + public static final int M_ANY = 0xFFFFFFFF; + public static final int M_EMPTY = 0; + public static final int M_PCDATA = 1 << 30; + public static final int M_ROOT = 1 << 31; + public static final int F_RESTART = 1; + public static final int F_CDATA = 2; + public static final int F_NOFORCE = 4; + private HashMap theEntities = + new HashMap(); // String -> Character + private HashMap theElementTypes = + new HashMap(); // String -> ElementType + private String theURI = ""; + private String thePrefix = ""; + private ElementType theRoot = null; + /** + Add or replace an element type for this schema. + @param name Name (Qname) of the element + @param model Models of the element's content as a vector of bits + @param memberOf Models the element is a member of as a vector of bits + @param flags Flags for the element + **/ + public void elementType(String name, int model, int memberOf, int flags) { + ElementType e = new ElementType(name, model, memberOf, flags, this); + theElementTypes.put(name.toLowerCase(Locale.ROOT), e); + if (memberOf == M_ROOT) theRoot = e; + } + /** + Get the root element of this schema + **/ + public ElementType rootElementType() { + return theRoot; + } + /** + Add or replace a default attribute for an element type in this schema. + @param elemName Name (Qname) of the element type + @param attrName Name (Qname) of the attribute + @param type Type of the attribute + @param value Default value of the attribute; null if no default + **/ + public void attribute(String elemName, String attrName, + String type, String value) { + ElementType e = getElementType(elemName); + if (e == null) { + throw new Error("Attribute " + attrName + + " specified for unknown element type " + + elemName); + } + e.setAttribute(attrName, type, value); + } + /** + Specify natural parent of an element in this schema. + @param name Name of the child element + @param parentName Name of the parent element + **/ + public void parent(String name, String parentName) { + ElementType child = getElementType(name); + ElementType parent = getElementType(parentName); + if (child == null) { + throw new Error("No child " + name + " for parent " + parentName); + } + if (parent == null) { + throw new Error("No parent " + parentName + " for child " + name); + } + child.setParent(parent); + } + /** + Add to or replace a character entity in this schema. + @param name Name of the entity + @param value Value of the entity + **/ + public void entity(String name, int value) { + theEntities.put(name, new Integer(value)); + } + /** + Get an ElementType by name. + @param name Name (Qname) of the element type + @return The corresponding ElementType + **/ + public ElementType getElementType(String name) { + return (ElementType)(theElementTypes.get(name.toLowerCase(Locale.ROOT))); + } + /** + Get an entity value by name. + @param name Name of the entity + @return The corresponding character, or 0 if none + **/ + public int getEntity(String name) { +// System.err.println("%% Looking up entity " + name); + Integer ch = (Integer)theEntities.get(name); + if (ch == null) return 0; + return ch.intValue(); + } + /** + Return the URI (namespace name) of this schema. + **/ + public String getURI() { + return theURI; + } + /** + Return the prefix of this schema. + **/ + public String getPrefix() { + return thePrefix; + } + /** + Change the URI (namespace name) of this schema. + **/ + public void setURI(String uri) { + theURI = uri; + } + /** + Change the prefix of this schema. + **/ + public void setPrefix(String prefix) { + thePrefix = prefix; + } +} \ No newline at end of file From df2bd63f4ab447631d1c77a95f5fa20380352669 Mon Sep 17 00:00:00 2001 From: brucetoo Date: Tue, 15 Jan 2019 10:37:34 +0800 Subject: [PATCH 03/13] [Android] Support RichText by a special way to render html string 1. optimize the rule of parse html attributes 2. add rgb color style support --- .../weex/adapter/IWxHtmlTagAdapter.java | 9 +- .../weex/ui/component/html/HtmlComponent.java | 73 +++----- .../ui/component/html/WxHtmlComponent.java | 4 +- .../html/adapter/DefaultHtmlTagAdapter.java | 37 ++--- .../component/html/htmlcompat/HtmlCompat.java | 48 +----- .../htmlcompat/HtmlToSpannedConverter.java | 47 ++---- .../component/html/htmlcompat/XmlUtils.java | 6 + .../ui/component/html/spans/WxBulletSpan.java | 156 ++++++++++++++++++ .../ui/component/html/spans/WxQuoteSpan.java | 57 +++++++ 9 files changed, 278 insertions(+), 159 deletions(-) create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxBulletSpan.java create mode 100644 android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxQuoteSpan.java diff --git a/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java b/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java index 8e9e12a740..87c580708a 100644 --- a/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java +++ b/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java @@ -21,7 +21,6 @@ import android.content.Context; import android.view.View; -import com.taobao.weex.ui.component.html.HtmlComponent; import com.taobao.weex.ui.component.html.WxHtmlComponent; /** Created by Bruce Too On 2019/1/10. At 09:39 */ @@ -31,13 +30,11 @@ public interface IWxHtmlTagAdapter { * com.taobao.weex.ui.component.html.adapter.DefaultHtmlTagAdapter} * * @param context current activity context - * @param tagType tag's type define in {@link HtmlComponent.ViewType} + * @param tagName tag's name * @param html html string * @return the custom native view self */ - public T getHtmlTagView( - Context context, WxHtmlComponent component, @HtmlComponent.ViewType int tagType, String html); + public T getHtmlTagView(Context context, WxHtmlComponent component, String tagName, String html); - public View.OnClickListener getTagViewClickListener( - @HtmlComponent.ViewType int tagType, String html); + public View.OnClickListener getTagViewClickListener(String tagName, String html); } diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java index af279e0667..2dec653c3c 100644 --- a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java @@ -18,17 +18,13 @@ */ package com.taobao.weex.ui.component.html; -import android.support.annotation.IntDef; -import android.support.annotation.StringDef; import android.text.TextUtils; import com.taobao.weex.utils.WXLogUtils; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -45,7 +41,7 @@ public class HtmlComponent { "\n" + "\n" + " \n" - + "