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..f35676ed63 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/adapter/IWxHtmlTagAdapter.java @@ -0,0 +1,40 @@ +/** + * 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.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 tagName tag's name + * @param html html string + * @return the custom native view self + */ + public T getHtmlTagView(Context context, WxHtmlComponent component, String tagName, String html); + + public View.OnClickListener getTagViewClickListener(View clickView, String tagName, String html); +} diff --git a/android/sdk/src/main/java/com/taobao/weex/common/Constants.java b/android/sdk/src/main/java/com/taobao/weex/common/Constants.java index b311077515..7f813da28a 100644 --- a/android/sdk/src/main/java/com/taobao/weex/common/Constants.java +++ b/android/sdk/src/main/java/com/taobao/weex/common/Constants.java @@ -347,6 +347,12 @@ interface SLOT_LIFECYCLE { String STOP_PROPAGATION_RAX = "stoppropagation"; String ONMESSAGE = "message"; String NATIVE_BACK = "nativeback"; + + //WxHtmlComponent's header & footer appear&disappear event + String HEADER_APPEAR = "headerAppear"; + String HEADER_DISAPPEAR = "headerDisappear"; + String FOOTER_APPEAR = "footerAppear"; + String FOOTER_DISAPPEAR = "footerDisappear"; } public interface PSEUDO { 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..671c18a9d9 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/HtmlComponent.java @@ -0,0 +1,244 @@ +/** + * 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.TextUtils; + +import com.taobao.weex.utils.WXLogUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +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 TAG_DEFAULT = "text"; + public static final String TAG_TABLE = "table"; + public static final String TAG_IMAGE = "img"; + public static final String TAG_VIDEO = "video"; + + public String tagName; + public String info; + + private static HtmlComponent getComponent(String tagName, String info) { + HtmlComponent htmlComponent = new HtmlComponent(); + htmlComponent.tagName = tagName; + htmlComponent.info = info; + return htmlComponent; + } + + private static Matcher matcher(String originHtml,String tagName) { + Pattern pattern = Pattern.compile(regexByTagName(tagName)); + return pattern.matcher(originHtml); + } + + private static String regexByTagName(String tagName) { + String regex; + if (tagName.endsWith(TAG_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, 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(TAG_DEFAULT, originHtml)); + } else { + int index = 0; + while (tagMatcher.find()) { + htmlComponents.add(HtmlComponent.getComponent(TAG_DEFAULT, tagSplit[index])); + htmlComponents.add( + HtmlComponent.getComponent(tagName, tagMatcher.group())); + WXLogUtils.e( + "find " + tagName + " -> start:" + tagMatcher.start() + " end:" + tagMatcher.end()); + index++; + } + htmlComponents.add(HtmlComponent.getComponent(TAG_DEFAULT, 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 + * 2、custom native view + * + * @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, 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(TAG_TABLE)) { + allTagNames.remove(TAG_TABLE); + allTagNames.add(0, TAG_TABLE); + } + for (String tagName : allTagNames) { + if (htmlComponents.size() == 0) { // parse first tag. named
.. + htmlComponents.addAll(getInnerComponents(originHtml, tagName)); + } else { + for (HtmlComponent htmlComponent : htmlComponents) { + if (htmlComponent.tagName.equalsIgnoreCase(TAG_DEFAULT)) { // only text-view tagName 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..fa393918a1 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/WxHtmlComponent.java @@ -0,0 +1,323 @@ +/** + * 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.graphics.Color; +import android.graphics.Rect; +import android.support.annotation.NonNull; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.alibaba.fastjson.JSONArray; +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, and we can + * add header & footer view inside this component,like below: + * + *
      Any Header View
      + *
      Any Footer View
      + *
      + * only parse({@link #addSubView(View, int)})the first two direct views, + * the first one as header({@link #mHeaderContainer}) + * the second one as footer({@link #mFooterContainer}) + */ +public class WxHtmlComponent extends WXVContainer { + + private static final int DEFAULT_PAGE_EDGE = 20; + private static final String TAG_DIRECT_CHILD = "direct_child"; + private static final int TAG_DIRECT_CHILD_ID = 1 + 2 << 24; + 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; + private FrameLayout mHeaderContainer; + private LinearLayout mCenterContainer; + private FrameLayout mFooterContainer; + private String[] mSupportedTags =new String[]{HtmlComponent.TAG_IMAGE, HtmlComponent.TAG_TABLE, + HtmlComponent.TAG_VIDEO}; + private ViewTreeObserver.OnScrollChangedListener mHeaderScrollListener = new HeaderScrollListener(); + private ViewTreeObserver.OnScrollChangedListener mFooterScrollListener = new FooterScrollListener(); + private Rect mHeaderRect = new Rect(); + private Rect mFooterRect = new Rect(); + private Rect mRootRect = new Rect(); + private boolean mIsHeaderShowing; + private boolean mIsFooterShowing; + + public WxHtmlComponent( + WXSDKInstance instance, WXVContainer parent, BasicComponentData basicComponentData) { + super(instance, parent, basicComponentData); + } + + @Override + protected ScrollView initComponentHostView(@NonNull final Context context) { + LinearLayout container = new LinearLayout(context); + mHeaderContainer = new FrameLayout(getContext()); + mCenterContainer = new LinearLayout(getContext()); + mFooterContainer = new FrameLayout(getContext()); + mFooterContainer.setBackgroundColor(Color.LTGRAY); + container.addView(mHeaderContainer); + container.addView(mCenterContainer); + container.addView(mFooterContainer); + mCenterContainer.setOrientation(LinearLayout.VERTICAL); + container.setOrientation(LinearLayout.VERTICAL); + ScrollView scrollView = new ScrollView(context); + scrollView.setVerticalScrollBarEnabled(false); + scrollView.addView( + container, FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT); + return scrollView; + } + + @Override + protected void onFinishLayout() { + super.onFinishLayout(); + getHostView().getLocalVisibleRect(mRootRect); + } + + @Override + public void addSubView(View child, int index) { + if (child == null || getRealView() == null) { + return; + } + if(index == 0){ //header view + //remark the first direct sub view + child.setTag(TAG_DIRECT_CHILD_ID, TAG_DIRECT_CHILD); + mHeaderContainer.removeAllViews(); + mHeaderContainer.getViewTreeObserver().addOnScrollChangedListener(mHeaderScrollListener); + mHeaderContainer.addView(child,FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + }else if(index == 1){ //footer view + mFooterContainer.removeAllViews(); + mFooterContainer.getViewTreeObserver().addOnScrollChangedListener(mFooterScrollListener); + mFooterContainer.addView(child,FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.WRAP_CONTENT); + } + //ignore other index.. + } + + @Override + public ViewGroup.LayoutParams getChildLayoutParams(WXComponent child, View childView, int width, int height, int left, int right, int top, int bottom) { + ViewGroup.LayoutParams lp = childView.getLayoutParams(); + if (lp == null) { + lp = new FrameLayout.LayoutParams(width, height); + } else { + lp.width = width; + lp.height = height; + } + + if (lp instanceof ViewGroup.MarginLayoutParams) { + View hostView = child.getHostView(); + //only set the top margin of the first direct sub view + if (hostView != null && TAG_DIRECT_CHILD.equals(hostView.getTag(TAG_DIRECT_CHILD_ID))) { + this.setMarginsSupportRTL((ViewGroup.MarginLayoutParams) lp, left, top, right, + bottom); + } + } + return lp; + } + + /** + * htmlOption: { image: { resize: 'cover' }, table: { template: '' }, + * tags:['image','table','video'] } + * Template related to {@link HtmlComponent#HTML_TEMPLATE} + * Tags indicate the support one,and default contains image,video,table + * Once add new tag support, need custom native view in {@link + * com.taobao.weex.ui.component.html.adapter.DefaultHtmlTagAdapter#getExtendTagView(String)} + * @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"); + if (image != null) { + mImageResize = WXUtils.getString(image.get(Constants.Name.RESIZE), "cover"); + } + // table + JSONObject table = option.getJSONObject("table"); + if (table != null) { + mTableTemplate = WXUtils.getString(table.get("template"), HtmlComponent.HTML_TEMPLATE); + } + //tags + JSONArray tags = option.getJSONArray("tags"); + if (tags != null && tags.size() != 0) { + mSupportedTags = new String[tags.size()]; + for (int i = 0; i < tags.size(); i++) { + mSupportedTags[i] = tags.getString(i); + } + } + } + + 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"), "")); + + if (mCenterContainer.getChildCount() != 0) { + mCenterContainer.removeAllViews(); + } + + if (mHtmlComponents.size() == 0) { + mHtmlComponents.addAll( + HtmlComponent.parseTags(htmlText, mSupportedTags)); + } + + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + for (HtmlComponent htmlComponent : mHtmlComponents) { + mCenterContainer.addView( + getInstance() + .getHtmlTextAdapter() + .getHtmlTagView(getContext(), this, htmlComponent.tagName, 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(); + mHeaderContainer.getViewTreeObserver().removeOnScrollChangedListener(mHeaderScrollListener); + mFooterContainer.getViewTreeObserver().removeOnScrollChangedListener(mFooterScrollListener); + mHtmlComponents.clear(); + } + + private class HeaderScrollListener implements ViewTreeObserver.OnScrollChangedListener { + @Override + public void onScrollChanged() { + if (mHeaderContainer != null && mHeaderContainer.getChildCount() != 0) { + mHeaderContainer.getLocalVisibleRect(mHeaderRect); + if (mHeaderRect.width() != 0) { + if (mHeaderRect.bottom < 0) { // out top + if (mIsHeaderShowing) { + getInstance().fireEvent(getRef(),Constants.Event.HEADER_DISAPPEAR); + mIsHeaderShowing = false; + WXLogUtils.i("ScrollChange", "header disappear"); + } + } else { // inside + if (!mIsHeaderShowing) { + getInstance().fireEvent(getRef(),Constants.Event.HEADER_APPEAR); + mIsHeaderShowing = true; + WXLogUtils.i("ScrollChange", "header appear"); + } + } + } + } + } + } + + private class FooterScrollListener implements ViewTreeObserver.OnScrollChangedListener { + @Override + public void onScrollChanged() { + if (mFooterContainer != null && mFooterContainer.getChildCount() != 0) { + mFooterContainer.getLocalVisibleRect(mFooterRect); + if (mFooterRect.width() != 0) { + if (mFooterRect.top > mRootRect.bottom) { // out bottom + if (mIsFooterShowing) { + getInstance().fireEvent(getRef(),Constants.Event.FOOTER_DISAPPEAR); + mIsFooterShowing = false; + WXLogUtils.i("ScrollChange", "footer disappear"); + } + } else { // inside + if (!mIsFooterShowing) { + getInstance().fireEvent(getRef(),Constants.Event.FOOTER_APPEAR); + mIsFooterShowing = true; + WXLogUtils.i("ScrollChange", "footer appear"); + } + } + } + } + } + } + +} 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..829c9fb2f4 --- /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) { + //TODO more tag support by textView (nested
    • etc..) + } + + /** 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..2be9c73dbb --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/adapter/DefaultHtmlTagAdapter.java @@ -0,0 +1,280 @@ +/** + * 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.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebView; +import android.widget.FrameLayout; +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; +import java.util.regex.Pattern; + +/** 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.5625f; // 16:9; + protected Context context; + protected WxHtmlComponent component; + /** All wrapped native views with there "src" value */ + protected Map mImageMap = new HashMap<>(); + + private static Pattern sWidthPattern; + private static Pattern sHeightPattern; + + @Override + public View getHtmlTagView( + Context context, WxHtmlComponent component, String tagName, String html) { + this.context = context; + this.component = component; + View tagView = null; + switch (tagName) { + case HtmlComponent.TAG_DEFAULT: + tagView = getDefaultTextView(html); + break; + case HtmlComponent.TAG_TABLE: + tagView = getDefaultTabView(html); + break; + case HtmlComponent.TAG_IMAGE: + tagView = getDefaultImageView(html); + break; + case HtmlComponent.TAG_VIDEO: + tagView = getDefaultVideo(html); + break; + default: // text + View extendTagView = getExtendTagView(tagName,html); + tagView = extendTagView == null ? getDefaultTextView(html) : extendTagView; + break; + } + return tagView; + } + + @Override + public View.OnClickListener getTagViewClickListener(View clickView, String tagName, String html) { + return new EmptyClickListener(); + } + + /** + * For custom view + * + * @param info html string + * @return tag's native view + */ + protected View getExtendTagView(String tagName,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; + } + + /** + * In my case,the video tag info may like below:
    \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..1e4ddde4e8 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/HtmlToSpannedConverter.java @@ -0,0 +1,866 @@ +/** + * 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.graphics.Color; +import android.graphics.Typeface; +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.ForegroundColorSpan; +import android.text.style.ParagraphStyle; +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.ui.component.html.spans.WxBulletSpan; +import com.taobao.weex.ui.component.html.spans.WxQuoteSpan; + +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.TagHandler mTagHandler; + private int mFlags; + private static Pattern sTextAlignPattern; + /** + * 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; + } + + HtmlToSpannedConverter(Context context, String source, + HtmlCompat.TagHandler tagHandler, HtmlCompat.SpanCallback spanCallback, + Parser parser, int flags) { + mContext = context; + mSource = source; + mSpannableStringBuilder = new SpannableStringBuilder(); + 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-tagName 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'); + } + } + + 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); + } + } + + 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 WxBulletSpan()); + } + + 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 WxQuoteSpan()); + } + + 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) { + if(repl instanceof WxQuoteSpan){ + setSpanFromMark(tag, text, obj, repl,new ForegroundColorSpan(((WxQuoteSpan) repl).getColor())); + }else { + setSpanFromMark(tag, text, obj, repl); + } + } + } + + private void startCssStyle(Editable text, Attributes attributes) { + String style = attributes.getValue("", "style"); + if (style != null) { + String[] attrs = style.split(";"); + if(attrs.length != 0){ + for (String attr : attrs) { + int colonIndex = attr.indexOf(":"); + int color = -1; + switch (attr.substring(0,colonIndex).toLowerCase().trim()){ + case "background": + case "background-color": + color = getHtmlColor(attr.substring(colonIndex + 1)); + if (color != -1) { + start(text, new Background(color | 0xFF000000)); + } + break; + case "color": + color = getHtmlColor(attr.substring(colonIndex + 1)); + if (color != -1) { + start(text, new Foreground(color | 0xFF000000)); + } + break; + case "text-decoration": + String textDecoration = attr.substring(colonIndex + 1); + if (textDecoration.equalsIgnoreCase("line-through")) { + start(text, new Strikethrough()); + } + break; + case "font-size": + String textSizeString = attr.substring(colonIndex + 1); + if (!TextUtils.isEmpty(textSizeString)) { + if (textSizeString.contains("px")) { + int textSize = + Integer.valueOf(textSizeString.substring(0,textSizeString.indexOf("px"))); + textSize *= mContext.getResources().getDisplayMetrics().density; + start(text, new AbsoluteSize(textSize)); + } + if (textSizeString.contains("em")) { + float textSize = Float.valueOf(textSizeString.substring(0,textSizeString.indexOf("em"))); + start(text, new RelativeSize(textSize)); + } + } + break; + } + } + } + } + } + + 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 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..0a9003bf9c --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/htmlcompat/XmlUtils.java @@ -0,0 +1,839 @@ +/** + * 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 com.taobao.weex.utils.WXResourceUtils; + +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; + } else if (nm.contains("rgb")){ + return WXResourceUtils.getColor(nm); + } + 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; + } else if (value.contains("rgb")){//rgb,argb + return WXResourceUtils.getColor(value); + } + 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/com/taobao/weex/ui/component/html/spans/WxBulletSpan.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxBulletSpan.java new file mode 100644 index 0000000000..b0aecb2e4f --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxBulletSpan.java @@ -0,0 +1,156 @@ +/** + * 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.spans; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Path.Direction; +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/11. At 14:17 + * TODO different level with different bullet + */ +public class WxBulletSpan implements LeadingMarginSpan, ParcelableSpan { + private final int mGapWidth; + private final boolean mWantColor; + private final int mColor; + + private static final int BULLET_RADIUS = 5; + private static Path sBulletPath = null; + public static final int STANDARD_GAP_WIDTH = 12; + public final int mLevel; + + public WxBulletSpan() { + mGapWidth = STANDARD_GAP_WIDTH; + mWantColor = false; + mColor = 0; + mLevel = 0; + } + + public WxBulletSpan(int gapWidth) { + mGapWidth = gapWidth; + mWantColor = false; + mColor = 0; + mLevel = 0; + } + + public WxBulletSpan(int gapWidth, int color) { + mGapWidth = gapWidth; + mWantColor = true; + mColor = color; + mLevel = 0; + } + + public WxBulletSpan(int gapWidth, int color,int level) { + mGapWidth = gapWidth; + mWantColor = true; + mColor = color; + mLevel = level; + } + + public WxBulletSpan(Parcel src) { + mGapWidth = src.readInt(); + mWantColor = src.readInt() != 0; + mColor = src.readInt(); + mLevel = src.readInt(); + } + + public int getSpanTypeId() { + return getSpanTypeIdInternal(); + } + + 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); + dest.writeInt(mWantColor ? 1 : 0); + dest.writeInt(mColor); + dest.writeInt(mLevel); + } + + public int getLeadingMargin(boolean first) { + return 2 * BULLET_RADIUS + 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(); + int oldcolor = 0; + + if (mWantColor) { + oldcolor = p.getColor(); + p.setColor(mColor); + } + + p.setStyle(Paint.Style.FILL); + + if (c.isHardwareAccelerated()) { + if (sBulletPath == null) { + sBulletPath = new Path(); + // Bullet is slightly better to avoid aliasing artifacts on mdpi devices. + sBulletPath.addCircle(0.0f, 0.0f, 1.2f * BULLET_RADIUS, Direction.CW); + } + + c.save(); + c.translate(x + dir * BULLET_RADIUS + 2, (top + bottom) / 2.0f); + c.drawPath(sBulletPath, p); + c.restore(); + } else { + c.drawCircle(x + dir * BULLET_RADIUS + 2, (top + bottom) / 2.0f, BULLET_RADIUS, p); + } + + if (mWantColor) { + p.setColor(oldcolor); + } + + p.setStyle(style); + } + } + + public void drawBulletWithLevel(Canvas c,int level){ + switch (level){ + case 0: + + break; + case 1: + + break; + } + } +} diff --git a/android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxQuoteSpan.java b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxQuoteSpan.java new file mode 100644 index 0000000000..3bbac4fcd8 --- /dev/null +++ b/android/sdk/src/main/java/com/taobao/weex/ui/component/html/spans/WxQuoteSpan.java @@ -0,0 +1,57 @@ +/** + * 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.spans; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.text.Layout; +import android.text.style.QuoteSpan; + +/** + * Created by Bruce Too On 2019/1/11. At 14:11 + */ +public class WxQuoteSpan extends QuoteSpan { + private static final int STRIPE_WIDTH = 8; + private static final int GAP_WIDTH = 14; + @Override + public int getColor() { + return 0xffDEDEDE; + } + + @Override + public int getLeadingMargin(boolean first) { + return STRIPE_WIDTH + GAP_WIDTH; + } + + 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 layout) { + Paint.Style style = p.getStyle(); + int color = p.getColor(); + + p.setStyle(Paint.Style.FILL); + p.setColor(getColor()); + + c.drawRect(x, top, x + dir * STRIPE_WIDTH, bottom, p); + + p.setStyle(style); + p.setColor(color); + } +} 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